UNPKG

47.7 kBJavaScriptView Raw
1import _extends from '@babel/runtime/helpers/esm/extends';
2import _objectWithoutPropertiesLoose from '@babel/runtime/helpers/esm/objectWithoutPropertiesLoose';
3
4//
5
6var charCodeOfDot = ".".charCodeAt(0);
7var reEscapeChar = /\\(\\)?/g;
8var rePropName = RegExp(
9// Match anything that isn't a dot or bracket.
10"[^.[\\]]+" + "|" +
11// Or match property names within brackets.
12"\\[(?:" +
13// Match a non-string expression.
14"([^\"'][^[]*)" + "|" +
15// Or match strings (supports escaping characters).
16"([\"'])((?:(?!\\2)[^\\\\]|\\\\.)*?)\\2" + ")\\]" + "|" +
17// Or match "" as the space between consecutive dots or empty brackets.
18"(?=(?:\\.|\\[\\])(?:\\.|\\[\\]|$))", "g");
19
20/**
21 * Converts `string` to a property path array.
22 *
23 * @private
24 * @param {string} string The string to convert.
25 * @returns {Array} Returns the property path array.
26 */
27var stringToPath = function stringToPath(string) {
28 var result = [];
29 if (string.charCodeAt(0) === charCodeOfDot) {
30 result.push("");
31 }
32 string.replace(rePropName, function (match, expression, quote, subString) {
33 var key = match;
34 if (quote) {
35 key = subString.replace(reEscapeChar, "$1");
36 } else if (expression) {
37 key = expression.trim();
38 }
39 result.push(key);
40 });
41 return result;
42};
43var keysCache = {};
44var keysRegex = /[.[\]]+/;
45var toPath = function toPath(key) {
46 if (key === null || key === undefined || !key.length) {
47 return [];
48 }
49 if (typeof key !== "string") {
50 throw new Error("toPath() expects a string");
51 }
52 if (keysCache[key] == null) {
53 /**
54 * The following patch fixes issue 456, introduced since v4.20.3:
55 *
56 * Before v4.20.3, i.e. in v4.20.2, a `key` like 'choices[]' would map to ['choices']
57 * (e.g. an array of choices used where 'choices[]' is name attribute of an input of type checkbox).
58 *
59 * Since v4.20.3, a `key` like 'choices[]' would map to ['choices', ''] which is wrong and breaks
60 * this kind of inputs e.g. in React.
61 *
62 * v4.20.3 introduced an unwanted breaking change, this patch fixes it, see the issue at the link below.
63 *
64 * @see https://github.com/final-form/final-form/issues/456
65 */
66 if (key.endsWith("[]")) {
67 // v4.20.2 (a `key` like 'choices[]' should map to ['choices'], which is fine).
68 keysCache[key] = key.split(keysRegex).filter(Boolean);
69 } else {
70 // v4.20.3 (a `key` like 'choices[]' maps to ['choices', ''], which breaks applications relying on inputs like `<input type="checkbox" name="choices[]" />`).
71 keysCache[key] = stringToPath(key);
72 }
73 }
74 return keysCache[key];
75};
76
77//
78var getIn = function getIn(state, complexKey) {
79 // Intentionally using iteration rather than recursion
80 var path = toPath(complexKey);
81 var current = state;
82 for (var i = 0; i < path.length; i++) {
83 var key = path[i];
84 if (current === undefined || current === null || typeof current !== "object" || Array.isArray(current) && isNaN(key)) {
85 return undefined;
86 }
87 current = current[key];
88 }
89 return current;
90};
91
92function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return typeof key === "symbol" ? key : String(key); }
93function _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); }
94var setInRecursor = function setInRecursor(current, index, path, value, destroyArrays) {
95 if (index >= path.length) {
96 // end of recursion
97 return value;
98 }
99 var key = path[index];
100
101 // determine type of key
102 if (isNaN(key)) {
103 var _extends2;
104 // object set
105 if (current === undefined || current === null) {
106 var _ref;
107 // recurse
108 var _result = setInRecursor(undefined, index + 1, path, value, destroyArrays);
109
110 // delete or create an object
111 return _result === undefined ? undefined : (_ref = {}, _ref[key] = _result, _ref);
112 }
113 if (Array.isArray(current)) {
114 throw new Error("Cannot set a non-numeric property on an array");
115 }
116 // current exists, so make a copy of all its values, and add/update the new one
117 var _result2 = setInRecursor(current[key], index + 1, path, value, destroyArrays);
118 if (_result2 === undefined) {
119 var numKeys = Object.keys(current).length;
120 if (current[key] === undefined && numKeys === 0) {
121 // object was already empty
122 return undefined;
123 }
124 if (current[key] !== undefined && numKeys <= 1) {
125 // only key we had was the one we are deleting
126 if (!isNaN(path[index - 1]) && !destroyArrays) {
127 // we are in an array, so return an empty object
128 return {};
129 } else {
130 return undefined;
131 }
132 }
133 current[key];
134 var _final = _objectWithoutPropertiesLoose(current, [key].map(_toPropertyKey));
135 return _final;
136 }
137 // set result in key
138 return _extends({}, current, (_extends2 = {}, _extends2[key] = _result2, _extends2));
139 }
140 // array set
141 var numericKey = Number(key);
142 if (current === undefined || current === null) {
143 // recurse
144 var _result3 = setInRecursor(undefined, index + 1, path, value, destroyArrays);
145
146 // if nothing returned, delete it
147 if (_result3 === undefined) {
148 return undefined;
149 }
150
151 // create an array
152 var _array = [];
153 _array[numericKey] = _result3;
154 return _array;
155 }
156 if (!Array.isArray(current)) {
157 throw new Error("Cannot set a numeric property on an object");
158 }
159 // recurse
160 var existingValue = current[numericKey];
161 var result = setInRecursor(existingValue, index + 1, path, value, destroyArrays);
162
163 // current exists, so make a copy of all its values, and add/update the new one
164 var array = [].concat(current);
165 if (destroyArrays && result === undefined) {
166 array.splice(numericKey, 1);
167 if (array.length === 0) {
168 return undefined;
169 }
170 } else {
171 array[numericKey] = result;
172 }
173 return array;
174};
175var setIn = function setIn(state, key, value, destroyArrays) {
176 if (destroyArrays === void 0) {
177 destroyArrays = false;
178 }
179 if (state === undefined || state === null) {
180 throw new Error("Cannot call setIn() with " + String(state) + " state");
181 }
182 if (key === undefined || key === null) {
183 throw new Error("Cannot call setIn() with " + String(key) + " key");
184 }
185 // Recursive function needs to accept and return State, but public API should
186 // only deal with Objects
187 return setInRecursor(state, 0, toPath(key), value, destroyArrays);
188};
189
190var FORM_ERROR = "FINAL_FORM/form-error";
191var ARRAY_ERROR = "FINAL_FORM/array-error";
192
193//
194
195
196/**
197 * Converts internal field state to published field state
198 */
199function publishFieldState(formState, field) {
200 var errors = formState.errors,
201 initialValues = formState.initialValues,
202 lastSubmittedValues = formState.lastSubmittedValues,
203 submitErrors = formState.submitErrors,
204 submitFailed = formState.submitFailed,
205 submitSucceeded = formState.submitSucceeded,
206 submitting = formState.submitting,
207 values = formState.values;
208 var active = field.active,
209 blur = field.blur,
210 change = field.change,
211 data = field.data,
212 focus = field.focus,
213 modified = field.modified,
214 modifiedSinceLastSubmit = field.modifiedSinceLastSubmit,
215 name = field.name,
216 touched = field.touched,
217 validating = field.validating,
218 visited = field.visited;
219 var value = getIn(values, name);
220 var error = getIn(errors, name);
221 if (error && error[ARRAY_ERROR]) {
222 error = error[ARRAY_ERROR];
223 }
224 var submitError = submitErrors && getIn(submitErrors, name);
225 var initial = initialValues && getIn(initialValues, name);
226 var pristine = field.isEqual(initial, value);
227 var dirtySinceLastSubmit = !!(lastSubmittedValues && !field.isEqual(getIn(lastSubmittedValues, name), value));
228 var valid = !error && !submitError;
229 return {
230 active: active,
231 blur: blur,
232 change: change,
233 data: data,
234 dirty: !pristine,
235 dirtySinceLastSubmit: dirtySinceLastSubmit,
236 error: error,
237 focus: focus,
238 initial: initial,
239 invalid: !valid,
240 length: Array.isArray(value) ? value.length : undefined,
241 modified: modified,
242 modifiedSinceLastSubmit: modifiedSinceLastSubmit,
243 name: name,
244 pristine: pristine,
245 submitError: submitError,
246 submitFailed: submitFailed,
247 submitSucceeded: submitSucceeded,
248 submitting: submitting,
249 touched: touched,
250 valid: valid,
251 value: value,
252 visited: visited,
253 validating: validating
254 };
255}
256
257//
258var fieldSubscriptionItems = ["active", "data", "dirty", "dirtySinceLastSubmit", "error", "initial", "invalid", "length", "modified", "modifiedSinceLastSubmit", "pristine", "submitError", "submitFailed", "submitSucceeded", "submitting", "touched", "valid", "value", "visited", "validating"];
259
260//
261
262var shallowEqual = function shallowEqual(a, b) {
263 if (a === b) {
264 return true;
265 }
266 if (typeof a !== "object" || !a || typeof b !== "object" || !b) {
267 return false;
268 }
269 var keysA = Object.keys(a);
270 var keysB = Object.keys(b);
271 if (keysA.length !== keysB.length) {
272 return false;
273 }
274 var bHasOwnProperty = Object.prototype.hasOwnProperty.bind(b);
275 for (var idx = 0; idx < keysA.length; idx++) {
276 var key = keysA[idx];
277 if (!bHasOwnProperty(key) || a[key] !== b[key]) {
278 return false;
279 }
280 }
281 return true;
282};
283
284//
285function subscriptionFilter (dest, src, previous, subscription, keys, shallowEqualKeys) {
286 var different = false;
287 keys.forEach(function (key) {
288 if (subscription[key]) {
289 dest[key] = src[key];
290 if (!previous || (~shallowEqualKeys.indexOf(key) ? !shallowEqual(src[key], previous[key]) : src[key] !== previous[key])) {
291 different = true;
292 }
293 }
294 });
295 return different;
296}
297
298//
299var shallowEqualKeys$1 = ["data"];
300
301/**
302 * Filters items in a FieldState based on a FieldSubscription
303 */
304var filterFieldState = function filterFieldState(state, previousState, subscription, force) {
305 var result = {
306 blur: state.blur,
307 change: state.change,
308 focus: state.focus,
309 name: state.name
310 };
311 var different = subscriptionFilter(result, state, previousState, subscription, fieldSubscriptionItems, shallowEqualKeys$1) || !previousState;
312 return different || force ? result : undefined;
313};
314
315//
316var formSubscriptionItems = ["active", "dirty", "dirtyFields", "dirtyFieldsSinceLastSubmit", "dirtySinceLastSubmit", "error", "errors", "hasSubmitErrors", "hasValidationErrors", "initialValues", "invalid", "modified", "modifiedSinceLastSubmit", "pristine", "submitting", "submitError", "submitErrors", "submitFailed", "submitSucceeded", "touched", "valid", "validating", "values", "visited"];
317
318//
319var shallowEqualKeys = ["touched", "visited"];
320
321/**
322 * Filters items in a FormState based on a FormSubscription
323 */
324function filterFormState(state, previousState, subscription, force) {
325 var result = {};
326 var different = subscriptionFilter(result, state, previousState, subscription, formSubscriptionItems, shallowEqualKeys) || !previousState;
327 return different || force ? result : undefined;
328}
329
330//
331var memoize = function memoize(fn) {
332 var lastArgs;
333 var lastResult;
334 return function () {
335 for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
336 args[_key] = arguments[_key];
337 }
338 if (!lastArgs || args.length !== lastArgs.length || args.some(function (arg, index) {
339 return !shallowEqual(lastArgs[index], arg);
340 })) {
341 lastArgs = args;
342 lastResult = fn.apply(void 0, args);
343 }
344 return lastResult;
345 };
346};
347
348var isPromise = (function (obj) {
349 return !!obj && (typeof obj === "object" || typeof obj === "function") && typeof obj.then === "function";
350});
351
352var version = "4.20.10";
353
354var configOptions = ["debug", "initialValues", "keepDirtyOnReinitialize", "mutators", "onSubmit", "validate", "validateOnBlur"];
355var tripleEquals = function tripleEquals(a, b) {
356 return a === b;
357};
358var hasAnyError = function hasAnyError(errors) {
359 return Object.keys(errors).some(function (key) {
360 var value = errors[key];
361 if (value && typeof value === "object" && !(value instanceof Error)) {
362 return hasAnyError(value);
363 }
364 return typeof value !== "undefined";
365 });
366};
367function convertToExternalFormState(_ref) {
368 var active = _ref.active,
369 dirtySinceLastSubmit = _ref.dirtySinceLastSubmit,
370 modifiedSinceLastSubmit = _ref.modifiedSinceLastSubmit,
371 error = _ref.error,
372 errors = _ref.errors,
373 initialValues = _ref.initialValues,
374 pristine = _ref.pristine,
375 submitting = _ref.submitting,
376 submitFailed = _ref.submitFailed,
377 submitSucceeded = _ref.submitSucceeded,
378 submitError = _ref.submitError,
379 submitErrors = _ref.submitErrors,
380 valid = _ref.valid,
381 validating = _ref.validating,
382 values = _ref.values;
383 return {
384 active: active,
385 dirty: !pristine,
386 dirtySinceLastSubmit: dirtySinceLastSubmit,
387 modifiedSinceLastSubmit: modifiedSinceLastSubmit,
388 error: error,
389 errors: errors,
390 hasSubmitErrors: !!(submitError || submitErrors && hasAnyError(submitErrors)),
391 hasValidationErrors: !!(error || hasAnyError(errors)),
392 invalid: !valid,
393 initialValues: initialValues,
394 pristine: pristine,
395 submitting: submitting,
396 submitFailed: submitFailed,
397 submitSucceeded: submitSucceeded,
398 submitError: submitError,
399 submitErrors: submitErrors,
400 valid: valid,
401 validating: validating > 0,
402 values: values
403 };
404}
405function notifySubscriber(subscriber, subscription, state, lastState, filter, force) {
406 var notification = filter(state, lastState, subscription, force);
407 if (notification) {
408 subscriber(notification);
409 return true;
410 }
411 return false;
412}
413function notify(_ref2, state, lastState, filter, force) {
414 var entries = _ref2.entries;
415 Object.keys(entries).forEach(function (key) {
416 var entry = entries[Number(key)];
417 // istanbul ignore next
418 if (entry) {
419 var subscription = entry.subscription,
420 subscriber = entry.subscriber,
421 notified = entry.notified;
422 if (notifySubscriber(subscriber, subscription, state, lastState, filter, force || !notified)) {
423 entry.notified = true;
424 }
425 }
426 });
427}
428function createForm(config) {
429 if (!config) {
430 throw new Error("No config specified");
431 }
432 var debug = config.debug,
433 destroyOnUnregister = config.destroyOnUnregister,
434 keepDirtyOnReinitialize = config.keepDirtyOnReinitialize,
435 initialValues = config.initialValues,
436 mutators = config.mutators,
437 onSubmit = config.onSubmit,
438 validate = config.validate,
439 validateOnBlur = config.validateOnBlur;
440 if (!onSubmit) {
441 throw new Error("No onSubmit function specified");
442 }
443 var state = {
444 subscribers: {
445 index: 0,
446 entries: {}
447 },
448 fieldSubscribers: {},
449 fields: {},
450 formState: {
451 asyncErrors: {},
452 dirtySinceLastSubmit: false,
453 modifiedSinceLastSubmit: false,
454 errors: {},
455 initialValues: initialValues && _extends({}, initialValues),
456 invalid: false,
457 pristine: true,
458 submitting: false,
459 submitFailed: false,
460 submitSucceeded: false,
461 resetWhileSubmitting: false,
462 valid: true,
463 validating: 0,
464 values: initialValues ? _extends({}, initialValues) : {}
465 },
466 lastFormState: undefined
467 };
468 var inBatch = 0;
469 var validationPaused = false;
470 var validationBlocked = false;
471 var preventNotificationWhileValidationPaused = false;
472 var nextAsyncValidationKey = 0;
473 var asyncValidationPromises = {};
474 var clearAsyncValidationPromise = function clearAsyncValidationPromise(key) {
475 return function (result) {
476 delete asyncValidationPromises[key];
477 return result;
478 };
479 };
480 var changeValue = function changeValue(state, name, mutate) {
481 var before = getIn(state.formState.values, name);
482 var after = mutate(before);
483 state.formState.values = setIn(state.formState.values, name, after) || {};
484 };
485 var renameField = function renameField(state, from, to) {
486 if (state.fields[from]) {
487 var _extends2, _extends3;
488 state.fields = _extends({}, state.fields, (_extends2 = {}, _extends2[to] = _extends({}, state.fields[from], {
489 name: to,
490 // rebind event handlers
491 blur: function blur() {
492 return api.blur(to);
493 },
494 change: function change(value) {
495 return api.change(to, value);
496 },
497 focus: function focus() {
498 return api.focus(to);
499 },
500 lastFieldState: undefined
501 }), _extends2));
502 delete state.fields[from];
503 state.fieldSubscribers = _extends({}, state.fieldSubscribers, (_extends3 = {}, _extends3[to] = state.fieldSubscribers[from], _extends3));
504 delete state.fieldSubscribers[from];
505 var value = getIn(state.formState.values, from);
506 state.formState.values = setIn(state.formState.values, from, undefined) || {};
507 state.formState.values = setIn(state.formState.values, to, value);
508 delete state.lastFormState;
509 }
510 };
511
512 // bind state to mutators
513 var getMutatorApi = function getMutatorApi(key) {
514 return function () {
515 // istanbul ignore next
516 if (mutators) {
517 // ^^ causes branch coverage warning, but needed to appease the Flow gods
518 var mutatableState = {
519 formState: state.formState,
520 fields: state.fields,
521 fieldSubscribers: state.fieldSubscribers,
522 lastFormState: state.lastFormState
523 };
524 for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
525 args[_key] = arguments[_key];
526 }
527 var returnValue = mutators[key](args, mutatableState, {
528 changeValue: changeValue,
529 getIn: getIn,
530 renameField: renameField,
531 resetFieldState: api.resetFieldState,
532 setIn: setIn,
533 shallowEqual: shallowEqual
534 });
535 state.formState = mutatableState.formState;
536 state.fields = mutatableState.fields;
537 state.fieldSubscribers = mutatableState.fieldSubscribers;
538 state.lastFormState = mutatableState.lastFormState;
539 runValidation(undefined, function () {
540 notifyFieldListeners();
541 notifyFormListeners();
542 });
543 return returnValue;
544 }
545 };
546 };
547 var mutatorsApi = mutators ? Object.keys(mutators).reduce(function (result, key) {
548 result[key] = getMutatorApi(key);
549 return result;
550 }, {}) : {};
551 var runRecordLevelValidation = function runRecordLevelValidation(setErrors) {
552 var promises = [];
553 if (validate) {
554 var errorsOrPromise = validate(_extends({}, state.formState.values)); // clone to avoid writing
555 if (isPromise(errorsOrPromise)) {
556 promises.push(errorsOrPromise.then(function (errors) {
557 return setErrors(errors, true);
558 }));
559 } else {
560 setErrors(errorsOrPromise, false);
561 }
562 }
563 return promises;
564 };
565 var getValidators = function getValidators(field) {
566 return Object.keys(field.validators).reduce(function (result, index) {
567 var validator = field.validators[Number(index)]();
568 if (validator) {
569 result.push(validator);
570 }
571 return result;
572 }, []);
573 };
574 var runFieldLevelValidation = function runFieldLevelValidation(field, setError) {
575 var promises = [];
576 var validators = getValidators(field);
577 if (validators.length) {
578 var error;
579 validators.forEach(function (validator) {
580 var errorOrPromise = validator(getIn(state.formState.values, field.name), state.formState.values, validator.length === 0 || validator.length === 3 ? publishFieldState(state.formState, state.fields[field.name]) : undefined);
581 if (errorOrPromise && isPromise(errorOrPromise)) {
582 field.validating = true;
583 var promise = errorOrPromise.then(function (error) {
584 if (state.fields[field.name]) {
585 state.fields[field.name].validating = false;
586 setError(error);
587 }
588 }); // errors must be resolved, not rejected
589 promises.push(promise);
590 } else if (!error) {
591 // first registered validator wins
592 error = errorOrPromise;
593 }
594 });
595 setError(error);
596 }
597 return promises;
598 };
599 var runValidation = function runValidation(fieldChanged, callback) {
600 if (validationPaused) {
601 validationBlocked = true;
602 callback();
603 return;
604 }
605 var fields = state.fields,
606 formState = state.formState;
607 var safeFields = _extends({}, fields);
608 var fieldKeys = Object.keys(safeFields);
609 if (!validate && !fieldKeys.some(function (key) {
610 return getValidators(safeFields[key]).length;
611 })) {
612 callback();
613 return; // no validation rules
614 }
615
616 // pare down field keys to actually validate
617 var limitedFieldLevelValidation = false;
618 if (fieldChanged) {
619 var changedField = safeFields[fieldChanged];
620 if (changedField) {
621 var validateFields = changedField.validateFields;
622 if (validateFields) {
623 limitedFieldLevelValidation = true;
624 fieldKeys = validateFields.length ? validateFields.concat(fieldChanged) : [fieldChanged];
625 }
626 }
627 }
628 var recordLevelErrors = {};
629 var asyncRecordLevelErrors = {};
630 var fieldLevelErrors = {};
631 var promises = [].concat(runRecordLevelValidation(function (errors, wasAsync) {
632 if (wasAsync) {
633 asyncRecordLevelErrors = errors || {};
634 } else {
635 recordLevelErrors = errors || {};
636 }
637 }), fieldKeys.reduce(function (result, name) {
638 return result.concat(runFieldLevelValidation(fields[name], function (error) {
639 fieldLevelErrors[name] = error;
640 }));
641 }, []));
642 var hasAsyncValidations = promises.length > 0;
643 var asyncValidationPromiseKey = ++nextAsyncValidationKey;
644 var promise = Promise.all(promises).then(clearAsyncValidationPromise(asyncValidationPromiseKey));
645
646 // backwards-compat: add promise to submit-blocking promises iff there are any promises to await
647 if (hasAsyncValidations) {
648 asyncValidationPromises[asyncValidationPromiseKey] = promise;
649 }
650 var processErrors = function processErrors(afterAsync) {
651 var merged = _extends({}, limitedFieldLevelValidation ? formState.errors : {}, recordLevelErrors, afterAsync ? asyncRecordLevelErrors // new async errors
652 : formState.asyncErrors);
653 var forEachError = function forEachError(fn) {
654 fieldKeys.forEach(function (name) {
655 if (fields[name]) {
656 // make sure field is still registered
657 // field-level errors take precedent over record-level errors
658 var recordLevelError = getIn(recordLevelErrors, name);
659 var errorFromParent = getIn(merged, name);
660 var hasFieldLevelValidation = getValidators(safeFields[name]).length;
661 var fieldLevelError = fieldLevelErrors[name];
662 fn(name, hasFieldLevelValidation && fieldLevelError || validate && recordLevelError || (!recordLevelError && !limitedFieldLevelValidation ? errorFromParent : undefined));
663 }
664 });
665 };
666 forEachError(function (name, error) {
667 merged = setIn(merged, name, error) || {};
668 });
669 forEachError(function (name, error) {
670 if (error && error[ARRAY_ERROR]) {
671 var existing = getIn(merged, name);
672 var copy = [].concat(existing);
673 copy[ARRAY_ERROR] = error[ARRAY_ERROR];
674 merged = setIn(merged, name, copy);
675 }
676 });
677 if (!shallowEqual(formState.errors, merged)) {
678 formState.errors = merged;
679 }
680 if (afterAsync) {
681 formState.asyncErrors = asyncRecordLevelErrors;
682 }
683 formState.error = recordLevelErrors[FORM_ERROR];
684 };
685 if (hasAsyncValidations) {
686 // async validations are running, ensure validating is true before notifying
687 state.formState.validating++;
688 callback();
689 }
690
691 // process sync errors
692 processErrors(false);
693 // sync errors have been set. notify listeners while we wait for others
694 callback();
695 if (hasAsyncValidations) {
696 var afterPromise = function afterPromise() {
697 state.formState.validating--;
698 callback();
699 // field async validation may affect formState validating
700 // so force notifyFormListeners if validating is still 0 after callback finished
701 // and lastFormState validating is true
702 if (state.formState.validating === 0 && state.lastFormState.validating) {
703 notifyFormListeners();
704 }
705 };
706 promise.then(function () {
707 if (nextAsyncValidationKey > asyncValidationPromiseKey) {
708 // if this async validator has been superseded by another, ignore its results
709 return;
710 }
711 processErrors(true);
712 }).then(afterPromise, afterPromise);
713 }
714 };
715 var notifyFieldListeners = function notifyFieldListeners(name) {
716 if (inBatch) {
717 return;
718 }
719 var fields = state.fields,
720 fieldSubscribers = state.fieldSubscribers,
721 formState = state.formState;
722 var safeFields = _extends({}, fields);
723 var notifyField = function notifyField(name) {
724 var field = safeFields[name];
725 var fieldState = publishFieldState(formState, field);
726 var lastFieldState = field.lastFieldState;
727 field.lastFieldState = fieldState;
728 var fieldSubscriber = fieldSubscribers[name];
729 if (fieldSubscriber) {
730 notify(fieldSubscriber, fieldState, lastFieldState, filterFieldState, lastFieldState === undefined);
731 }
732 };
733 if (name) {
734 notifyField(name);
735 } else {
736 Object.keys(safeFields).forEach(notifyField);
737 }
738 };
739 var markAllFieldsTouched = function markAllFieldsTouched() {
740 Object.keys(state.fields).forEach(function (key) {
741 state.fields[key].touched = true;
742 });
743 };
744 var hasSyncErrors = function hasSyncErrors() {
745 return !!(state.formState.error || hasAnyError(state.formState.errors));
746 };
747 var calculateNextFormState = function calculateNextFormState() {
748 var fields = state.fields,
749 formState = state.formState,
750 lastFormState = state.lastFormState;
751 var safeFields = _extends({}, fields);
752 var safeFieldKeys = Object.keys(safeFields);
753
754 // calculate dirty/pristine
755 var foundDirty = false;
756 var dirtyFields = safeFieldKeys.reduce(function (result, key) {
757 var dirty = !safeFields[key].isEqual(getIn(formState.values, key), getIn(formState.initialValues || {}, key));
758 if (dirty) {
759 foundDirty = true;
760 result[key] = true;
761 }
762 return result;
763 }, {});
764 var dirtyFieldsSinceLastSubmit = safeFieldKeys.reduce(function (result, key) {
765 // istanbul ignore next
766 var nonNullLastSubmittedValues = formState.lastSubmittedValues || {}; // || {} is for flow, but causes branch coverage complaint
767 if (!safeFields[key].isEqual(getIn(formState.values, key), getIn(nonNullLastSubmittedValues, key))) {
768 result[key] = true;
769 }
770 return result;
771 }, {});
772 formState.pristine = !foundDirty;
773 formState.dirtySinceLastSubmit = !!(formState.lastSubmittedValues && Object.values(dirtyFieldsSinceLastSubmit).some(function (value) {
774 return value;
775 }));
776 formState.modifiedSinceLastSubmit = !!(formState.lastSubmittedValues &&
777 // Object.values would treat values as mixed (facebook/flow#2221)
778 Object.keys(safeFields).some(function (value) {
779 return safeFields[value].modifiedSinceLastSubmit;
780 }));
781 formState.valid = !formState.error && !formState.submitError && !hasAnyError(formState.errors) && !(formState.submitErrors && hasAnyError(formState.submitErrors));
782 var nextFormState = convertToExternalFormState(formState);
783 var _safeFieldKeys$reduce = safeFieldKeys.reduce(function (result, key) {
784 result.modified[key] = safeFields[key].modified;
785 result.touched[key] = safeFields[key].touched;
786 result.visited[key] = safeFields[key].visited;
787 return result;
788 }, {
789 modified: {},
790 touched: {},
791 visited: {}
792 }),
793 modified = _safeFieldKeys$reduce.modified,
794 touched = _safeFieldKeys$reduce.touched,
795 visited = _safeFieldKeys$reduce.visited;
796 nextFormState.dirtyFields = lastFormState && shallowEqual(lastFormState.dirtyFields, dirtyFields) ? lastFormState.dirtyFields : dirtyFields;
797 nextFormState.dirtyFieldsSinceLastSubmit = lastFormState && shallowEqual(lastFormState.dirtyFieldsSinceLastSubmit, dirtyFieldsSinceLastSubmit) ? lastFormState.dirtyFieldsSinceLastSubmit : dirtyFieldsSinceLastSubmit;
798 nextFormState.modified = lastFormState && shallowEqual(lastFormState.modified, modified) ? lastFormState.modified : modified;
799 nextFormState.touched = lastFormState && shallowEqual(lastFormState.touched, touched) ? lastFormState.touched : touched;
800 nextFormState.visited = lastFormState && shallowEqual(lastFormState.visited, visited) ? lastFormState.visited : visited;
801 return lastFormState && shallowEqual(lastFormState, nextFormState) ? lastFormState : nextFormState;
802 };
803 var callDebug = function callDebug() {
804 return debug && "development" !== "production" && debug(calculateNextFormState(), Object.keys(state.fields).reduce(function (result, key) {
805 result[key] = state.fields[key];
806 return result;
807 }, {}));
808 };
809 var notifying = false;
810 var scheduleNotification = false;
811 var notifyFormListeners = function notifyFormListeners() {
812 if (notifying) {
813 scheduleNotification = true;
814 } else {
815 notifying = true;
816 callDebug();
817 if (!inBatch && !(validationPaused && preventNotificationWhileValidationPaused)) {
818 var lastFormState = state.lastFormState;
819 var nextFormState = calculateNextFormState();
820 if (nextFormState !== lastFormState) {
821 state.lastFormState = nextFormState;
822 notify(state.subscribers, nextFormState, lastFormState, filterFormState);
823 }
824 }
825 notifying = false;
826 if (scheduleNotification) {
827 scheduleNotification = false;
828 notifyFormListeners();
829 }
830 }
831 };
832 var beforeSubmit = function beforeSubmit() {
833 return Object.keys(state.fields).some(function (name) {
834 return state.fields[name].beforeSubmit && state.fields[name].beforeSubmit() === false;
835 });
836 };
837 var afterSubmit = function afterSubmit() {
838 return Object.keys(state.fields).forEach(function (name) {
839 return state.fields[name].afterSubmit && state.fields[name].afterSubmit();
840 });
841 };
842 var resetModifiedAfterSubmit = function resetModifiedAfterSubmit() {
843 return Object.keys(state.fields).forEach(function (key) {
844 return state.fields[key].modifiedSinceLastSubmit = false;
845 });
846 };
847
848 // generate initial errors
849 runValidation(undefined, function () {
850 notifyFormListeners();
851 });
852 var api = {
853 batch: function batch(fn) {
854 inBatch++;
855 fn();
856 inBatch--;
857 notifyFieldListeners();
858 notifyFormListeners();
859 },
860 blur: function blur(name) {
861 var fields = state.fields,
862 formState = state.formState;
863 var previous = fields[name];
864 if (previous) {
865 // can only blur registered fields
866 delete formState.active;
867 fields[name] = _extends({}, previous, {
868 active: false,
869 touched: true
870 });
871 if (validateOnBlur) {
872 runValidation(name, function () {
873 notifyFieldListeners();
874 notifyFormListeners();
875 });
876 } else {
877 notifyFieldListeners();
878 notifyFormListeners();
879 }
880 }
881 },
882 change: function change(name, value) {
883 var fields = state.fields,
884 formState = state.formState;
885 if (getIn(formState.values, name) !== value) {
886 changeValue(state, name, function () {
887 return value;
888 });
889 var previous = fields[name];
890 if (previous) {
891 // only track modified for registered fields
892 fields[name] = _extends({}, previous, {
893 modified: true,
894 modifiedSinceLastSubmit: !!formState.lastSubmittedValues
895 });
896 }
897 if (validateOnBlur) {
898 notifyFieldListeners();
899 notifyFormListeners();
900 } else {
901 runValidation(name, function () {
902 notifyFieldListeners();
903 notifyFormListeners();
904 });
905 }
906 }
907 },
908 get destroyOnUnregister() {
909 return !!destroyOnUnregister;
910 },
911 set destroyOnUnregister(value) {
912 destroyOnUnregister = value;
913 },
914 focus: function focus(name) {
915 var field = state.fields[name];
916 if (field && !field.active) {
917 state.formState.active = name;
918 field.active = true;
919 field.visited = true;
920 notifyFieldListeners();
921 notifyFormListeners();
922 }
923 },
924 mutators: mutatorsApi,
925 getFieldState: function getFieldState(name) {
926 var field = state.fields[name];
927 return field && field.lastFieldState;
928 },
929 getRegisteredFields: function getRegisteredFields() {
930 return Object.keys(state.fields);
931 },
932 getState: function getState() {
933 return calculateNextFormState();
934 },
935 initialize: function initialize(data) {
936 var fields = state.fields,
937 formState = state.formState;
938 var safeFields = _extends({}, fields);
939 var values = typeof data === "function" ? data(formState.values) : data;
940 if (!keepDirtyOnReinitialize) {
941 formState.values = values;
942 }
943 /**
944 * Hello, inquisitive code reader! Thanks for taking the time to dig in!
945 *
946 * The following code is the way it is to allow for non-registered deep
947 * field values to be set via initialize()
948 */
949
950 // save dirty values
951 var savedDirtyValues = keepDirtyOnReinitialize ? Object.keys(safeFields).reduce(function (result, key) {
952 var field = safeFields[key];
953 var pristine = field.isEqual(getIn(formState.values, key), getIn(formState.initialValues || {}, key));
954 if (!pristine) {
955 result[key] = getIn(formState.values, key);
956 }
957 return result;
958 }, {}) : {};
959 // update initalValues and values
960 formState.initialValues = values;
961 formState.values = values;
962 // restore the dirty values
963 Object.keys(savedDirtyValues).forEach(function (key) {
964 formState.values = setIn(formState.values, key, savedDirtyValues[key]) || {};
965 });
966 runValidation(undefined, function () {
967 notifyFieldListeners();
968 notifyFormListeners();
969 });
970 },
971 isValidationPaused: function isValidationPaused() {
972 return validationPaused;
973 },
974 pauseValidation: function pauseValidation(preventNotification) {
975 if (preventNotification === void 0) {
976 preventNotification = true;
977 }
978 validationPaused = true;
979 preventNotificationWhileValidationPaused = preventNotification;
980 },
981 registerField: function registerField(name, subscriber, subscription, fieldConfig) {
982 if (subscription === void 0) {
983 subscription = {};
984 }
985 if (!state.fieldSubscribers[name]) {
986 state.fieldSubscribers[name] = {
987 index: 0,
988 entries: {}
989 };
990 }
991 var index = state.fieldSubscribers[name].index++;
992
993 // save field subscriber callback
994 state.fieldSubscribers[name].entries[index] = {
995 subscriber: memoize(subscriber),
996 subscription: subscription,
997 notified: false
998 };
999
1000 // create initial field state if not exists
1001 var field = state.fields[name] || {
1002 active: false,
1003 afterSubmit: fieldConfig && fieldConfig.afterSubmit,
1004 beforeSubmit: fieldConfig && fieldConfig.beforeSubmit,
1005 data: fieldConfig && fieldConfig.data || {},
1006 isEqual: fieldConfig && fieldConfig.isEqual || tripleEquals,
1007 lastFieldState: undefined,
1008 modified: false,
1009 modifiedSinceLastSubmit: false,
1010 name: name,
1011 touched: false,
1012 valid: true,
1013 validateFields: fieldConfig && fieldConfig.validateFields,
1014 validators: {},
1015 validating: false,
1016 visited: false
1017 };
1018 // Mutators can create a field in order to keep the field states
1019 // We must update this field when registerField is called afterwards
1020 field.blur = field.blur || function () {
1021 return api.blur(name);
1022 };
1023 field.change = field.change || function (value) {
1024 return api.change(name, value);
1025 };
1026 field.focus = field.focus || function () {
1027 return api.focus(name);
1028 };
1029 state.fields[name] = field;
1030 var haveValidator = false;
1031 var silent = fieldConfig && fieldConfig.silent;
1032 var notify = function notify() {
1033 if (silent && state.fields[name]) {
1034 notifyFieldListeners(name);
1035 } else {
1036 notifyFormListeners();
1037 notifyFieldListeners();
1038 }
1039 };
1040 if (fieldConfig) {
1041 haveValidator = !!(fieldConfig.getValidator && fieldConfig.getValidator());
1042 if (fieldConfig.getValidator) {
1043 state.fields[name].validators[index] = fieldConfig.getValidator;
1044 }
1045 var noValueInFormState = getIn(state.formState.values, name) === undefined;
1046 if (fieldConfig.initialValue !== undefined && (noValueInFormState || getIn(state.formState.values, name) === getIn(state.formState.initialValues, name))
1047 // only initialize if we don't yet have any value for this field
1048 ) {
1049 state.formState.initialValues = setIn(state.formState.initialValues || {}, name, fieldConfig.initialValue);
1050 state.formState.values = setIn(state.formState.values, name, fieldConfig.initialValue);
1051 runValidation(undefined, notify);
1052 }
1053
1054 // only use defaultValue if we don't yet have any value for this field
1055 if (fieldConfig.defaultValue !== undefined && fieldConfig.initialValue === undefined && getIn(state.formState.initialValues, name) === undefined && noValueInFormState) {
1056 state.formState.values = setIn(state.formState.values, name, fieldConfig.defaultValue);
1057 }
1058 }
1059 if (haveValidator) {
1060 runValidation(undefined, notify);
1061 } else {
1062 notify();
1063 }
1064 return function () {
1065 var validatorRemoved = false;
1066 // istanbul ignore next
1067 if (state.fields[name]) {
1068 // state.fields[name] may have been removed by a mutator
1069 validatorRemoved = !!(state.fields[name].validators[index] && state.fields[name].validators[index]());
1070 delete state.fields[name].validators[index];
1071 }
1072 var hasFieldSubscribers = !!state.fieldSubscribers[name];
1073 if (hasFieldSubscribers) {
1074 // state.fieldSubscribers[name] may have been removed by a mutator
1075 delete state.fieldSubscribers[name].entries[index];
1076 }
1077 var lastOne = hasFieldSubscribers && !Object.keys(state.fieldSubscribers[name].entries).length;
1078 if (lastOne) {
1079 delete state.fieldSubscribers[name];
1080 delete state.fields[name];
1081 if (validatorRemoved) {
1082 state.formState.errors = setIn(state.formState.errors, name, undefined) || {};
1083 }
1084 if (destroyOnUnregister) {
1085 state.formState.values = setIn(state.formState.values, name, undefined, true) || {};
1086 }
1087 }
1088 if (!silent) {
1089 if (validatorRemoved) {
1090 runValidation(undefined, function () {
1091 notifyFormListeners();
1092 notifyFieldListeners();
1093 });
1094 } else if (lastOne) {
1095 // values or errors may have changed
1096 notifyFormListeners();
1097 }
1098 }
1099 };
1100 },
1101 reset: function reset(initialValues) {
1102 if (initialValues === void 0) {
1103 initialValues = state.formState.initialValues;
1104 }
1105 if (state.formState.submitting) {
1106 state.formState.resetWhileSubmitting = true;
1107 }
1108 state.formState.submitFailed = false;
1109 state.formState.submitSucceeded = false;
1110 delete state.formState.submitError;
1111 delete state.formState.submitErrors;
1112 delete state.formState.lastSubmittedValues;
1113 api.initialize(initialValues || {});
1114 },
1115 /**
1116 * Resets all field flags (e.g. touched, visited, etc.) to their initial state
1117 */
1118 resetFieldState: function resetFieldState(name) {
1119 state.fields[name] = _extends({}, state.fields[name], {
1120 active: false,
1121 lastFieldState: undefined,
1122 modified: false,
1123 touched: false,
1124 valid: true,
1125 validating: false,
1126 visited: false
1127 });
1128 runValidation(undefined, function () {
1129 notifyFieldListeners();
1130 notifyFormListeners();
1131 });
1132 },
1133 /**
1134 * Returns the form to a clean slate; that is:
1135 * - Clear all values
1136 * - Resets all fields to their initial state
1137 */
1138 restart: function restart(initialValues) {
1139 if (initialValues === void 0) {
1140 initialValues = state.formState.initialValues;
1141 }
1142 api.batch(function () {
1143 for (var name in state.fields) {
1144 api.resetFieldState(name);
1145 state.fields[name] = _extends({}, state.fields[name], {
1146 active: false,
1147 lastFieldState: undefined,
1148 modified: false,
1149 modifiedSinceLastSubmit: false,
1150 touched: false,
1151 valid: true,
1152 validating: false,
1153 visited: false
1154 });
1155 }
1156 api.reset(initialValues);
1157 });
1158 },
1159 resumeValidation: function resumeValidation() {
1160 validationPaused = false;
1161 preventNotificationWhileValidationPaused = false;
1162 if (validationBlocked) {
1163 // validation was attempted while it was paused, so run it now
1164 runValidation(undefined, function () {
1165 notifyFieldListeners();
1166 notifyFormListeners();
1167 });
1168 }
1169 validationBlocked = false;
1170 },
1171 setConfig: function setConfig(name, value) {
1172 switch (name) {
1173 case "debug":
1174 debug = value;
1175 break;
1176 case "destroyOnUnregister":
1177 destroyOnUnregister = value;
1178 break;
1179 case "initialValues":
1180 api.initialize(value);
1181 break;
1182 case "keepDirtyOnReinitialize":
1183 keepDirtyOnReinitialize = value;
1184 break;
1185 case "mutators":
1186 mutators = value;
1187 if (value) {
1188 Object.keys(mutatorsApi).forEach(function (key) {
1189 if (!(key in value)) {
1190 delete mutatorsApi[key];
1191 }
1192 });
1193 Object.keys(value).forEach(function (key) {
1194 mutatorsApi[key] = getMutatorApi(key);
1195 });
1196 } else {
1197 Object.keys(mutatorsApi).forEach(function (key) {
1198 delete mutatorsApi[key];
1199 });
1200 }
1201 break;
1202 case "onSubmit":
1203 onSubmit = value;
1204 break;
1205 case "validate":
1206 validate = value;
1207 runValidation(undefined, function () {
1208 notifyFieldListeners();
1209 notifyFormListeners();
1210 });
1211 break;
1212 case "validateOnBlur":
1213 validateOnBlur = value;
1214 break;
1215 default:
1216 throw new Error("Unrecognised option " + name);
1217 }
1218 },
1219 submit: function submit() {
1220 var formState = state.formState;
1221 if (formState.submitting) {
1222 return;
1223 }
1224 delete formState.submitErrors;
1225 delete formState.submitError;
1226 formState.lastSubmittedValues = _extends({}, formState.values);
1227 if (hasSyncErrors()) {
1228 markAllFieldsTouched();
1229 resetModifiedAfterSubmit();
1230 state.formState.submitFailed = true;
1231 notifyFormListeners();
1232 notifyFieldListeners();
1233 return; // no submit for you!!
1234 }
1235
1236 var asyncValidationPromisesKeys = Object.keys(asyncValidationPromises);
1237 if (asyncValidationPromisesKeys.length) {
1238 // still waiting on async validation to complete...
1239 Promise.all(asyncValidationPromisesKeys.map(function (key) {
1240 return asyncValidationPromises[Number(key)];
1241 })).then(api.submit, console.error);
1242 return;
1243 }
1244 var submitIsBlocked = beforeSubmit();
1245 if (submitIsBlocked) {
1246 return;
1247 }
1248 var resolvePromise;
1249 var completeCalled = false;
1250 var complete = function complete(errors) {
1251 formState.submitting = false;
1252 var resetWhileSubmitting = formState.resetWhileSubmitting;
1253 if (resetWhileSubmitting) {
1254 formState.resetWhileSubmitting = false;
1255 }
1256 if (errors && hasAnyError(errors)) {
1257 formState.submitFailed = true;
1258 formState.submitSucceeded = false;
1259 formState.submitErrors = errors;
1260 formState.submitError = errors[FORM_ERROR];
1261 markAllFieldsTouched();
1262 } else {
1263 if (!resetWhileSubmitting) {
1264 formState.submitFailed = false;
1265 formState.submitSucceeded = true;
1266 }
1267 afterSubmit();
1268 }
1269 notifyFormListeners();
1270 notifyFieldListeners();
1271 completeCalled = true;
1272 if (resolvePromise) {
1273 resolvePromise(errors);
1274 }
1275 return errors;
1276 };
1277 formState.submitting = true;
1278 formState.submitFailed = false;
1279 formState.submitSucceeded = false;
1280 formState.lastSubmittedValues = _extends({}, formState.values);
1281 resetModifiedAfterSubmit();
1282
1283 // onSubmit is either sync, callback or async with a Promise
1284 var result = onSubmit(formState.values, api, complete);
1285 if (!completeCalled) {
1286 if (result && isPromise(result)) {
1287 // onSubmit is async with a Promise
1288 notifyFormListeners(); // let everyone know we are submitting
1289 notifyFieldListeners(); // notify fields also
1290 return result.then(complete, function (error) {
1291 complete();
1292 throw error;
1293 });
1294 } else if (onSubmit.length >= 3) {
1295 // must be async, so we should return a Promise
1296 notifyFormListeners(); // let everyone know we are submitting
1297 notifyFieldListeners(); // notify fields also
1298 return new Promise(function (resolve) {
1299 resolvePromise = resolve;
1300 });
1301 } else {
1302 // onSubmit is sync
1303 complete(result);
1304 }
1305 }
1306 },
1307 subscribe: function subscribe(subscriber, subscription) {
1308 if (!subscriber) {
1309 throw new Error("No callback given.");
1310 }
1311 if (!subscription) {
1312 throw new Error("No subscription provided. What values do you want to listen to?");
1313 }
1314 var memoized = memoize(subscriber);
1315 var subscribers = state.subscribers;
1316 var index = subscribers.index++;
1317 subscribers.entries[index] = {
1318 subscriber: memoized,
1319 subscription: subscription,
1320 notified: false
1321 };
1322 var nextFormState = calculateNextFormState();
1323 notifySubscriber(memoized, subscription, nextFormState, nextFormState, filterFormState, true);
1324 return function () {
1325 delete subscribers.entries[index];
1326 };
1327 }
1328 };
1329 return api;
1330}
1331
1332export { ARRAY_ERROR, FORM_ERROR, configOptions, createForm, fieldSubscriptionItems, formSubscriptionItems, getIn, setIn, version };