UNPKG

informed

Version:

A lightweight framework and utility for building powerful forms in React applications

1,323 lines (1,237 loc) 68.9 kB
import { createClass as _createClass, typeof as _typeof, toConsumableArray as _toConsumableArray, objectSpread2 as _objectSpread2, classCallCheck as _classCallCheck } from './_virtual/_rollupPluginBabelHelpers.js'; import { ObjectMap } from './ObjectMap.js'; import { Debug } from './debug.js'; import { isChild, informedParse, informedFormat, validateYupField, uuidv4, validateYupSchema, validateAjvSchema, getSchemaPathFromJsonPath, debounceByName } from './utils.js'; var debug = Debug('informed:FormController' + '\t'); var initializeValue = function initializeValue(value, _ref) { var formatter = _ref.formatter, parser = _ref.parser, initialize = _ref.initialize, mask = _ref.mask; if (value != null) { // Call users initialize if it was passed if (initialize && !parser) { return initialize(value); } if (formatter && !parser) { var res = informedFormat(value, formatter); return res.value; } if (mask && !parser) { return mask(value); } return value; } // Not needed but called out specifically return undefined; }; var initializeMask = function initializeMask(value, _ref2) { var formatter = _ref2.formatter, initialize = _ref2.initialize, mask = _ref2.mask; if (initialize) { return initialize(value); } // Call formatter if (formatter) { var res = informedFormat(value, formatter); return res.value; } if (mask) { return mask(value); } return value; }; /* ----------------------- FormController ----------------------- */ var FormController = /*#__PURE__*/function () { function FormController(options) { var _this$options$current; _classCallCheck(this, FormController); // Set the options this.options = options; // Initialize listeners this.subscriptions = new Map(); // Get schema stuff off of options var _options$current = options.current, ajv = _options$current.ajv, schema = _options$current.schema, fieldMap = _options$current.fieldMap, adapter = _options$current.adapter; // Create new ajv instance if passed this.ajv = ajv ? new ajv({ allErrors: true }) : null; // TODO this fucks with json pointer stuff // if (ajvErrors) { // ajvErrors(this.ajv); // } this.ajvValidate = ajv ? this.ajv.compile(schema) : null; // Add field map ( defaults to our field map ) this.fieldMap = adapter || fieldMap; // This is the emitter lol this.emitter = this; // Map will store all fields by name // Key => name // Val => fieldMetaRef // Why? so the form knows about field meta this.fieldsMap = new Map(); // Map will store current validation request // Key => name // Val => {uuid, value} // Why? So we know if validation request is stale or not // We ALSO need to store value because of edge case: // // Assume sync validation "Must be at least 5 characters" and some async validation that takes 2 seconds // 1. User types ddddd ( 5 inputs so we pass sync validation ) // 2. Because there is no sync validation async will trigger to validate username // 3. While that occurs, user starts to Backspace the ddddd // 4. The second user backspaces, sync has error so async never "re-occurs" // 5. the sync request made on step 2 completes // 6. It wipes out sync error this.validationRequests = new Map(); this.dataRequests = new Map(); // For array fields lol this.removalLocked = undefined; // Initialize the controller state this.state = { pristine: true, dirty: false, submitted: false, invalid: false, valid: true, submitting: false, validating: 0, gathering: 0, values: {}, errors: {}, touched: {}, maskedValues: {}, dirt: {}, focused: {}, modified: {}, data: {}, initialValues: this.options.current.initialValues || {}, disabled: (_this$options$current = this.options.current.disabled) !== null && _this$options$current !== void 0 ? _this$options$current : false, memory: {} }; // Bind functions that will be called externally this.getValue = this.getValue.bind(this); this.setValue = this.setValue.bind(this); this.setValueQuietly = this.setValueQuietly.bind(this); this.setValues = this.setValues.bind(this); this.setTheseValues = this.setTheseValues.bind(this); this.resetPath = this.resetPath.bind(this); this.getMaskedValue = this.getMaskedValue.bind(this); this.setMaskedValue = this.setMaskedValue.bind(this); this.setModifiedValue = this.setModifiedValue.bind(this); this.getTouched = this.getTouched.bind(this); this.setTouched = this.setTouched.bind(this); this.getFocused = this.getFocused.bind(this); this.setFocused = this.setFocused.bind(this); this.getError = this.getError.bind(this); this.setError = this.setError.bind(this); this.reset = this.reset.bind(this); this.validate = this.validate.bind(this); this.asyncValidate = this.asyncValidate.bind(this); this.getDirty = this.getDirty.bind(this); this.setDirt = this.setDirt.bind(this); this.getPristine = this.getPristine.bind(this); this.getFormState = this.getFormState.bind(this); this.getFormApi = this.getFormApi.bind(this); this.getFieldState = this.getFieldState.bind(this); this.getValid = this.getValid.bind(this); this.on = this.on.bind(this); this.emit = this.emit.bind(this); this.removeListener = this.removeListener.bind(this); this.remove = this.remove.bind(this); this.swap = this.swap.bind(this); this.insert = this.insert.bind(this); this.pullOut = this.pullOut.bind(this); this.register = this.register.bind(this); this.deregister = this.deregister.bind(this); this.getInitialValue = this.getInitialValue.bind(this); this.initialize = this.initialize.bind(this); this.reformat = this.reformat.bind(this); this.lockRemoval = this.lockRemoval.bind(this); this.unlockRemoval = this.unlockRemoval.bind(this); this.resetField = this.resetField.bind(this); this.getRemovalLocked = this.getRemovalLocked.bind(this); this.isRemovalLocked = this.isRemovalLocked.bind(this); this.submitForm = this.submitForm.bind(this); this.touchAllFields = this.touchAllFields.bind(this); this.keyDown = this.keyDown.bind(this); this.validateAsync = this.validateAsync.bind(this); this.gatherData = this.gatherData.bind(this); this.validated = this.validated.bind(this); this.debouncedValidateAsync = debounceByName(this.validateAsync, this.options.current.debounceError); this.debouncedGatherInfo = debounceByName(this.gatherData, this.options.current.debounceGather); this.getOptions = this.getOptions.bind(this); this.validateField = this.validateField.bind(this); this.getErrorMessage = this.getErrorMessage.bind(this); this.clearValue = this.clearValue.bind(this); this.clearAllValues = this.clearAllValues.bind(this); this.clearError = this.clearError.bind(this); this.clearAllErrors = this.clearAllErrors.bind(this); this.getData = this.getData.bind(this); this.setData = this.setData.bind(this); this.getModified = this.getModified.bind(this); this.updateValid = this.updateValid.bind(this); this.focusFirstError = this.focusFirstError.bind(this); this.scrollToFirstError = this.scrollToFirstError.bind(this); this.setPristine = this.setPristine.bind(this); this.disableForm = this.disableForm.bind(this); this.enableForm = this.enableForm.bind(this); this.getMemory = this.getMemory.bind(this); this.restore = this.restore.bind(this); this.fieldExists = this.fieldExists.bind(this); } _createClass(FormController, [{ key: "getOptions", value: function getOptions() { return this.options.current; } }, { key: "getMemory", value: function getMemory(name) { return ObjectMap.get(this.state.memory, name); } }, { key: "getValue", value: function getValue(name) { return ObjectMap.get(this.state.values, name); } }, { key: "getMaskedValue", value: function getMaskedValue(name) { return ObjectMap.get(this.state.maskedValues, name); } }, { key: "fieldExists", value: function fieldExists(name) { return !!this.fieldsMap.get(name); } }, { key: "setMaskedValue", value: function setMaskedValue(name, value) { return ObjectMap.set(this.state.maskedValues, name, value); } }, { key: "setModifiedValue", value: function setModifiedValue(name, value) { return ObjectMap.set(this.state.modified, name, value); } }, { key: "updateValid", value: function updateValid() { // Store previous state var prevValid = this.state.valid; // Now update this.state.valid = ObjectMap.empty(this.state.errors); this.state.invalid = !this.state.valid; // Call change handlers if needed if (prevValid && !this.state.valid) { this.emit('invalid'); } if (!prevValid && this.state.valid) { this.emit('valid'); } } }, { key: "setValues", value: function setValues(values) { this.fieldsMap.forEach(function (fieldMeta) { // Get value out of values object basd on path var val = ObjectMap.get(values, fieldMeta.current.name); fieldMeta.current.fieldApi.setValue(val); }); } }, { key: "disableForm", value: function disableForm() { this.disabled = true; this.state.disabled = true; // This will make all fields re render with updated value this.emit('field', '_ALL_'); } }, { key: "enableForm", value: function enableForm() { this.disabled = undefined; this.state.disabled = false; // This will make all fields re render with updated value this.emit('field', '_ALL_'); } }, { key: "setTheseValues", value: function setTheseValues(values) { this.fieldsMap.forEach(function (fieldMeta) { // Get value out of values object basd on path var val = ObjectMap.get(values, fieldMeta.current.name); // Only set if it is there if (val != null) { fieldMeta.current.fieldApi.setValue(val); } }); } }, { key: "resetPath", value: function resetPath(path) { this.fieldsMap.forEach(function (fieldMeta) { // Only reset if parent path if (isChild(path, fieldMeta.current.name)) { fieldMeta.current.fieldApi.reset(); } }); } }, { key: "restore", value: function restore(name) { this.setValue(name, this.getMemory(name)); } }, { key: "setValueQuietly", value: function setValueQuietly(name, value) { this.setValue(name, value, undefined, undefined, true); } }, { key: "setValue", value: function setValue(name, value, e, key, quiet) { var _this$fieldsMap$get; debug("setValue ".concat(name), value); var hasTrigger = e && _typeof(e) === 'object' && e.triggers; // Avoid set loops by determining if this field originated this set if (hasTrigger && e.triggers.includes(name)) { debug("NOT setting ".concat(name, " as it exists in the transitive path ").concat(JSON.stringify(e.triggers, null, 2))); return; } // Get meta for field var meta = ((_this$fieldsMap$get = this.fieldsMap.get(name)) === null || _this$fieldsMap$get === void 0 ? void 0 : _this$fieldsMap$get.current) || {}; // Remember Cursor position! // Need try catch because of Safari Bullshit issue try { if (e && e.target && e.target.selectionStart) { meta.setCursor(e.target.selectionStart, key); } } catch (e) { // Need try catch because of Safari Bullshit issue if (!(e instanceof TypeError)) { throw e; } } // Before we update the value lets save the previous one in a variable var previousValue = this.getValue(name); if (value === '') { if (meta.allowEmptyString) { var emptyValue = (meta === null || meta === void 0 ? void 0 : meta.type) === 'number' ? 0 : value; // Override emptyValue if explicitly set if (meta.emptyValue) { emptyValue = meta.emptyValue; } debug("Setting ".concat(name, "'s value to ").concat(emptyValue, " because allowEmptyString is set")); ObjectMap.set(this.state.values, name, emptyValue); // Special if check for modified if (meta.getInitialValue && meta.getInitialValue() != emptyValue) { ObjectMap.set(this.state.modified, name, emptyValue); } else { debug("Removing ".concat(name, "'s modified")); ObjectMap["delete"](this.state.modified, emptyValue); } ObjectMap.set(this.state.maskedValues, name, value); } else { debug("Setting ".concat(name, "'s value to undefiend")); ObjectMap.set(this.state.values, name, undefined); ObjectMap.set(this.state.modified, name, undefined); ObjectMap.set(this.state.maskedValues, name, undefined); } } else if ((meta === null || meta === void 0 ? void 0 : meta.type) === 'number' && value !== undefined) { var val = value; var maskedVal = value; // call mask if passed if (meta.mask && !meta.maskOnBlur) { maskedVal = meta.mask(val); } // // Only parse if parser was passed if (meta.parser) { val = val != null ? informedParse(val, meta.parser) : val; } debug("Setting ".concat(name, "'s value to ").concat(+val)); ObjectMap.set(this.state.values, name, +val); // Special if check for modified if (meta.getInitialValue && meta.getInitialValue() != val || // Always set for modifiedOnMount meta.modifyOnMount) { ObjectMap.set(this.state.modified, name, +val); } else { // Note: Important that we set to undefined and NOT call delete // Why? See readme 4.44.2 debug("Removing ".concat(name, "'s modified")); ObjectMap.set(this.state.modified, name, undefined); } debug("Setting ".concat(name, "'s maskedValue to"), +maskedVal); ObjectMap.set(this.state.maskedValues, name, +maskedVal); } else { var _val = value; var _maskedVal = value; // Only clean if clean was passed if (meta.clean) { _val = meta.clean(_val); _maskedVal = _val; } // Call formatter parser if passed if (meta.formatter) { var res = informedFormat(_val, meta.formatter, this.getMaskedValue(name), meta.dir); meta.setCursorOffset(res.offset, key); _maskedVal = res.value; _val = _maskedVal; } // call mask if passed if (meta.mask && !meta.maskOnBlur) { _val = meta.mask(_val); _maskedVal = _val; } // // Only parse if parser was passed if (meta.parser) { _val = _val != null ? informedParse(_val, meta.parser) : _val; } // vvvvvvvvvvvvv VALUE UPDATE OCCURS HERE vvvvvvvvvvvv debug("Setting ".concat(name, "'s value to"), _val); ObjectMap.set(this.state.values, name, _val); // Special if check for modified // We want to set even if field is not on screen ( does not have getter for initial ) if (!meta.getInitialValue || meta.getInitialValue() != _val || // Always set for modifiedOnMount meta.modifyOnMount) { debug("Setting ".concat(name, "'s modified to"), _val); ObjectMap.set(this.state.modified, name, _val); } else { // Note: Important that we set to undefined and NOT call delete // Why? See readme 4.44.2 debug("Removing ".concat(name, "'s modified")); ObjectMap.set(this.state.modified, name, undefined); } debug("Setting ".concat(name, "'s maskedValue to"), _maskedVal); ObjectMap.set(this.state.maskedValues, name, _maskedVal); } // We only need to call validate if the user gave us one // and they want us to validate on change // Example validateOn = "change" ("change-change")==> true // Example validateOn = "blur" ("blur-blur") ==> false // Example validateOn = "submit" ("submit-submit")==> false // Example validateOn = "change-blur" ==> true // Example validateOn = "change-submit" ==> true // Example validateOn = "blur-submit" ==> false if (meta.validate && meta.validateOn.includes('change')) { var _val2 = ObjectMap.get(this.state.values, name); debug("Validating after change ".concat(name, " ").concat(_val2)); ObjectMap.set(this.state.errors, name, meta.validate(_val2, this.state.values)); } // Same thing but for YUP schema // I dont think we need this anymore as its done by the generate function ..... TODO maybe remove if (meta.yupSchema && meta.validateOn.includes('change')) { // Only call if we dont already have error if (this.getError(name) === undefined) { var _val3 = ObjectMap.get(this.state.values, name); debug("Validating YUP after change ".concat(name, " ").concat(_val3)); ObjectMap.set(this.state.errors, name, validateYupField(meta.yupSchema, _val3)); } } // We only need to call asyncValidate if // 1. the user gave us one // 2. they want us to validate on change // 3. We don't have a sync error // Example validateOn = "change" ("change-change")==> true // Example validateOn = "blur" ("blur-blur") ==> false // Example validateOn = "submit" ("submit-submit")==> false // Example validateOn = "change-blur" ==> false // Example validateOn = "change-submit" ==> false // Example validateOn = "blur-submit" ==> false if (meta.asyncValidate && meta.validateOn === 'change') { // Get error to determine if we even want to validateAsync if (this.getError(name) === undefined) this.debouncedValidateAsync(name); } // Always remember to update pristine and valid here if (!quiet) { this.state.pristine = false; this.state.dirty = !this.state.pristine; ObjectMap.set(this.state.dirt, name, true); } // Remember to update valid this.updateValid(); // Call users onChange if it exists if (meta.onChange) { var fieldState = this.getFieldState(name); // Add the previous value to the field state object fieldState.previousValue = previousValue; meta.onChange(fieldState, e); } // Call users onNativeChange if we had native event and func if (e && meta.onNativeChange) { var _fieldState = this.getFieldState(name); // Add the previous value to the field state object _fieldState.previousValue = previousValue; meta.onNativeChange(_fieldState, e); } // Emit native event // Transitive sets are special // Example: // A Triggers B // B Triggers C // C Triggers A // // A ( trigger1 ) ----> B ( trigger2 )----> C ( trigger3 ) ----> A ( trigger ) ----> B LOOP // // To aviod this we need to pass the triggers to the event if (hasTrigger) { this.emit('field-native', name, [].concat(_toConsumableArray(e.triggers), [name])); } else if (e) { this.emit('field-native', name, [name]); } if (meta.gatherData && !meta.gatherOnBlur) { // Get error to determine if we even want to validateAsync this.debouncedGatherInfo(name); } // Normal field event this.emit('field', name); // Special event when fields value changes this.emit('field-value', name); this.emit('field-modified', name); this.emit('field-value-set', name); } }, { key: "validateField", value: function validateField(name) { var _this$fieldsMap$get2; // Get meta for field var meta = (_this$fieldsMap$get2 = this.fieldsMap.get(name)) === null || _this$fieldsMap$get2 === void 0 ? void 0 : _this$fieldsMap$get2.current; if (!meta) return; if (meta.validate) { var val = ObjectMap.get(this.state.values, name); debug("Validating field ".concat(name, " via validateField with value ").concat(val)); ObjectMap.set(this.state.errors, name, meta.validate(val, this.state.values)); } // Same thing but for YUP schema if (meta.yupSchema) { // Only call if we dont already have error if (this.getError(name) === undefined) { var _val4 = ObjectMap.get(this.state.values, name); debug("Validating YUP field via validateField ".concat(name, " ").concat(_val4)); ObjectMap.set(this.state.errors, name, validateYupField(meta.yupSchema, _val4)); } } // TODO maybe do async validation here !?!?!?! // Remember to update valid this.updateValid(); this.emit('field', name); } }, { key: "getModified", value: function getModified(name) { return ObjectMap.get(this.state.modified, name); } }, { key: "getFocused", value: function getFocused(name) { return ObjectMap.get(this.state.focused, name); } }, { key: "setFocused", value: function setFocused(name, value, e) { var _this$fieldsMap$get3; debug("Setting ".concat(name, "'s focused to ").concat(value)); // Get meta for field var meta = ((_this$fieldsMap$get3 = this.fieldsMap.get(name)) === null || _this$fieldsMap$get3 === void 0 ? void 0 : _this$fieldsMap$get3.current) || {}; // Update the state ObjectMap.set(this.state.focused, name, value); // Call users onFoucs if it exists if (meta.onFocus) { var fieldState = this.getFieldState(name); meta.onFocus(fieldState, e); } // emit field update this.emit('field', name); } }, { key: "getTouched", value: function getTouched(name) { return ObjectMap.get(this.state.touched, name); } }, { key: "setTouched", value: function setTouched(name, value, e) { var _this$fieldsMap$get4; debug("Setting ".concat(name, "'s touched to ").concat(value)); // Get meta for field var meta = ((_this$fieldsMap$get4 = this.fieldsMap.get(name)) === null || _this$fieldsMap$get4 === void 0 ? void 0 : _this$fieldsMap$get4.current) || {}; // Update the state ObjectMap.set(this.state.touched, name, value); // Update value if maskOnBlur and we have mask if (meta.mask && meta.maskOnBlur) { var val = ObjectMap.get(this.state.values, name); var maskedVal = val; maskedVal = meta.mask(val); // // Only parse if parser was passed if (meta.parser) { val = val != null ? informedParse(val, meta.parser) : val; } debug("Setting ".concat(name, "'s value to"), maskedVal); ObjectMap.set(this.state.values, name, maskedVal); debug("Setting ".concat(name, "'s maskedValue to"), maskedVal); ObjectMap.set(this.state.maskedValues, name, maskedVal); } // We only need to call validate if the user gave us one // and they want us to validate on blur // Example validateOn = "change" ("change-change")==> true // Example validateOn = "blur" ("blur-blur") ==> true // Example validateOn = "submit" ("submit-submit")==> false // Example validateOn = "change-blur" ==> true // Example validateOn = "change-submit" ==> true // Example validateOn = "blur-submit" ==> true if (meta.validate && (meta.validateOn.includes('blur') || meta.validateOn.includes('change'))) { var _val5 = ObjectMap.get(this.state.values, name); debug("Validating after blur ".concat(name, " ").concat(_val5)); ObjectMap.set(this.state.errors, name, meta.validate(_val5, this.state.values)); } // We only need to call asyncValidate if // 1. the user gave us one // 2. they want us to validate on blur // 3. We don't have a sync error // Example validateOn = "change" ("change-change")==> true // Example validateOn = "blur" ("blur-blur") ==> true // Example validateOn = "submit" ("submit-submit")==> false // Example validateOn = "change-blur" ==> true // Example validateOn = "change-submit" ==> false // Example validateOn = "blur-submit" ==> false if (meta.asyncValidate && (meta.validateOn === 'blur' || meta.validateOn === 'change-blur' || meta.validateOn === 'change')) { // Get error to determine if we even want to validateAsync if (this.getError(name) === undefined) { this.validateAsync(name); } } // Remember to update valid this.updateValid(); // Gather data on blur if user passed gatherOnBlur if (meta.gatherData && meta.gatherOnBlur) { this.debouncedGatherInfo(name); } // Call users onBlur if it exists if (meta.onBlur) { var fieldState = this.getFieldState(name); meta.onBlur(fieldState, e); } this.emit('field', name); } }, { key: "getData", value: function getData(name) { return ObjectMap.get(this.state.data, name); } }, { key: "setData", value: function setData(name, value) { debug("Setting ".concat(name, "'s data to ").concat(value)); ObjectMap.set(this.state.data, name, value); this.emit('field', name); } }, { key: "getError", value: function getError(name) { return ObjectMap.get(this.state.errors, name); } }, { key: "setError", value: function setError(name, value) { debug("Setting ".concat(name, "'s error to ").concat(value)); ObjectMap.set(this.state.errors, name, value); this.state.valid = ObjectMap.empty(this.state.errors); this.state.invalid = !this.state.valid; this.emit('field', name); } }, { key: "getInitialValue", value: function getInitialValue(name) { return ObjectMap.get(this.state.initialValues, name); } }, { key: "getDirty", value: function getDirty(name) { return !!ObjectMap.get(this.state.dirt, name); } }, { key: "setDirt", value: function setDirt(name, value) { return ObjectMap.set(this.state.dirt, name, value); } }, { key: "getPristine", value: function getPristine(name) { return !this.getDirty(name); } }, { key: "getValid", value: function getValid(name) { // Valid when we have no error return ObjectMap.get(this.state.errors, name) === undefined; } }, { key: "getFormState", value: function getFormState() { return this.state; } }, { key: "clearValue", value: function clearValue(name) { this.setValue(name, undefined); this.emit('clear', name); } }, { key: "clearAllValues", value: function clearAllValues() { var _this = this; this.fieldsMap.forEach(function (fieldMeta) { _this.clearValue(fieldMeta.current.name); }); } }, { key: "clearError", value: function clearError(name) { this.setError(name, undefined); } }, { key: "clearAllErrors", value: function clearAllErrors() { var _this2 = this; this.fieldsMap.forEach(function (fieldMeta) { _this2.clearError(fieldMeta.current.name); }); } }, { key: "setPristine", value: function setPristine(pristine) { this.state.pristine = pristine; this.state.dirty = !this.state.pristine; // Just need to trigger a form state update this.emit('field'); } }, { key: "getFormApi", value: function getFormApi() { return { getValue: this.getValue, setValue: this.setValue, setValueQuietly: this.setValueQuietly, getMaskedValue: this.getMaskedValue, setMaskedValue: this.setMaskedValue, setModifiedValue: this.setModifiedValue, getTouched: this.getTouched, setTouched: this.setTouched, getError: this.getError, setError: this.setError, getFocused: this.getFocused, setFocused: this.setFocused, getData: this.getData, setData: this.setData, getModified: this.getModified, resetField: this.resetField, reset: this.reset, getFormState: this.getFormState, getPristine: this.getPristine, getDirty: this.getDirty, setDirt: this.setDirt, validateField: this.validateField, getFieldState: this.getFieldState, getInitialValue: this.getInitialValue, touchAllFields: this.touchAllFields, validate: this.validate, asyncValidate: this.asyncValidate, setValues: this.setValues, setTheseValues: this.setTheseValues, resetPath: this.resetPath, submitForm: this.submitForm, clearValue: this.clearValue, clearAllValues: this.clearAllValues, clearError: this.clearError, clearAllErrors: this.clearAllErrors, focusFirstError: this.focusFirstError, setPristine: this.setPristine, disable: this.disableForm, enable: this.enableForm, restore: this.restore, getMemory: this.getMemory, fieldExists: this.fieldExists }; } }, { key: "getFieldState", value: function getFieldState(name) { var _this$fieldsMap$get5; // Get meta for field var meta = ((_this$fieldsMap$get5 = this.fieldsMap.get(name)) === null || _this$fieldsMap$get5 === void 0 ? void 0 : _this$fieldsMap$get5.current) || {}; var error = this.getError(name); var focused = !!this.getFocused(name); var modified = !!this.getModified(name); var dirty = this.getDirty(name); var valid = this.getValid(name); var touched = !!this.getTouched(name); var pristine = !dirty; var validating = !!this.validationRequests.get(name); var gathering = !!this.dataRequests.get(name); var memory = this.getMemory(name); var showError = false; if (meta && meta.showErrorIfError) { showError = error !== undefined; } else if (meta && meta.showErrorIfDirty) { showError = error !== undefined && (dirty || touched); } else if (meta && meta.showErrorIfTouched) { showError = error !== undefined && touched; } // $relevant // $focused return { value: this.getValue(name), maskedValue: this.getMaskedValue(name), modified: modified, touched: touched, error: this.getError(name), data: this.getData(name), pristine: pristine, dirty: dirty, valid: valid, invalid: !valid, showError: showError, validating: validating, gathering: gathering, focused: focused, memory: memory }; } }, { key: "remove", value: function remove(name) { var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; var meta = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; debug('Remove', name); if (!this.removalLocked) { var _options$value = options.value, keepValue = _options$value === void 0 ? false : _options$value, _options$error = options.error, keepError = _options$error === void 0 ? false : _options$error, _options$touched = options.touched, keepTouched = _options$touched === void 0 ? false : _options$touched; // If user passed in remember for this field add it to memory if (meta.remember) { var valueToRemember = this.getValue(name); // Only remember if there is something to remember ( that way we dont wipe previous memory ) if (valueToRemember != undefined) { debug('Remembering', name, valueToRemember); ObjectMap.set(this.state.memory, name, valueToRemember); } } if (!keepValue) { debug('Delete Value', name); ObjectMap["delete"](this.state.values, name); debug('Delete Modified', name); ObjectMap["delete"](this.state.modified, name); debug('Delete Masked', name); ObjectMap["delete"](this.state.maskedValues, name); } if (!keepTouched) { debug('Delete Touched', name); ObjectMap["delete"](this.state.touched, name); } if (!keepError) { debug('Delete Errors', name); ObjectMap["delete"](this.state.errors, name); } debug('Delete Dirt', name); ObjectMap["delete"](this.state.dirt, name); debug('Delete Focused', name); ObjectMap["delete"](this.state.focused, name); debug('Delete Info', name); ObjectMap["delete"](this.state.data, name); // Remember to update valid this.updateValid(); // Final field change this.emit('field', name); // Special event when fields value changes this.emit('field-value', name); this.emit('field-modified', name); } else { debug('Removal locked so NOT removing', name); } } }, { key: "swap", value: function swap(name, a, b) { debug('Swap', name, a, b); debug('Swap Values'); ObjectMap.swap(this.state.values, name, a, b); debug('Swap Modified'); ObjectMap.swap(this.state.modified, name, a, b); debug('Swap MaskedValues'); ObjectMap.swap(this.state.maskedValues, name, a, b); debug('Swap Touched'); ObjectMap.swap(this.state.touched, name, a, b); debug('Swap Errors'); ObjectMap.swap(this.state.errors, name, a, b); debug('Swap Dirt'); ObjectMap.swap(this.state.dirt, name, a, b); debug('Swap Focused'); ObjectMap.swap(this.state.focused, name, a, b); debug('Swap Data'); ObjectMap.swap(this.state.data, name, a, b); // DO NOT emit event here we want to delay it on purpose because otherwise relevance will trigger with bad state // this.emit("field", name); this.state.pristine = false; this.state.dirty = !this.state.pristine; } }, { key: "insert", value: function insert(name, index, value) { debug('Insert', name, index, value); debug('Insert into Values'); ObjectMap.insert(this.state.values, name, index, value); debug('Insert into Modified'); ObjectMap.insert(this.state.modified, name, index, undefined); debug('Insert into MaskedValues'); ObjectMap.insert(this.state.maskedValues, name, index, value); debug('Insert into Touched'); ObjectMap.insert(this.state.touched, name, index, undefined); debug('Insert into Errors'); ObjectMap.insert(this.state.errors, name, index, undefined); debug('Insert into Dirt'); ObjectMap.insert(this.state.dirt, name, index, undefined); debug('Insert into Focused'); ObjectMap.insert(this.state.focused, name, index, undefined); debug('Insert into Data'); ObjectMap.insert(this.state.data, name, index, undefined); // Update pristine and dirty state this.state.pristine = false; this.state.dirty = !this.state.pristine; } }, { key: "pullOut", value: function pullOut(name) { debug('Pull', name); debug("Pull ".concat(name, " from values")); ObjectMap["delete"](this.state.values, name); debug("Pull ".concat(name, " from modified")); ObjectMap["delete"](this.state.modified, name); debug("Pull ".concat(name, " from maskedValues")); ObjectMap["delete"](this.state.maskedValues, name); debug("Pull ".concat(name, " from touched")); ObjectMap["delete"](this.state.touched, name); debug("Pull ".concat(name, " from errors")); ObjectMap["delete"](this.state.errors, name); debug("Pull ".concat(name, " from dirt")); ObjectMap["delete"](this.state.dirt, name); debug("Pull ".concat(name, " from focused")); ObjectMap["delete"](this.state.focused, name); debug("Pull ".concat(name, " from data")); ObjectMap["delete"](this.state.data, name); // DO NOT emit event here we want to delay it on purpose because otherwise relevance will trigger with bad state // this.emit("field", name); this.state.pristine = false; this.state.dirty = !this.state.pristine; } }, { key: "register", value: function register(name, meta) { debug('Register', name, meta); // Register the meta if (!this.fieldsMap.get(name)) { this.fieldsMap.set(name, meta); this.emit('field', name); } } }, { key: "deregister", value: function deregister(name) { if (this.fieldsMap.get(name)) { debug('De-Register', name); this.fieldsMap["delete"](name); this.emit('field', name); } } // Third parameter is to prevent any form renders when it first gets initialized }, { key: "initialize", value: function initialize(name, meta) { var emit = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true; debug('Initialize', name, 'emit:', emit, 'state:', this.state); // Initialize value if needed // If we already have value i.e "saved" // use that ( it was not removed on purpose! ) // Otherwise use the fields initial value if (this.getValue(name) === undefined && meta.current.initialValue != null && (meta.current.initializeValueIfPristine ? this.state.pristine : true)) { var _meta$current = meta.current, formatter = _meta$current.formatter, parser = _meta$current.parser, initialize = _meta$current.initialize, clean = _meta$current.clean, mask = _meta$current.mask, modifyOnMount = _meta$current.modifyOnMount; // Clean value if we have clean function var cleanedValue = clean ? clean(meta.current.initialValue) : meta.current.initialValue; var initialValue = initializeValue(cleanedValue, { formatter: formatter, parser: parser, initialize: initialize, mask: mask }); var initialMask = initializeMask(cleanedValue, { formatter: formatter, initialize: initialize, mask: mask }); debug("Initializing ".concat(name, "'s value to ").concat(initialValue)); ObjectMap.set(this.state.values, name, initialValue); debug("Initializing ".concat(name, "'s maskedValue to ").concat(initialMask)); ObjectMap.set(this.state.maskedValues, name, initialMask); if (modifyOnMount) { debug("Initializing ".concat(name, "'s modified to ").concat(initialValue, " because modifyOnMount was passed.")); ObjectMap.set(this.state.modified, name, initialValue); } } // Might need to set initial error if (meta.current.validate && meta.current.validateOnMount) { var val = ObjectMap.get(this.state.values, name); debug("Validating on mount ".concat(name, " ").concat(val), this.state); ObjectMap.set(this.state.errors, name, meta.current.validate(val, this.state.values)); } // validateOnMount="sync" DONT validateOnMount={true} DO if (meta.current.asyncValidate && meta.current.validateOnMount === true) { // Get error to determine if we even want to validateAsync if (this.getError(name) === undefined) this.validateAsync(name); } // Check if the form is valid this.state.valid = ObjectMap.empty(this.state.errors); this.state.invalid = !this.state.valid; if (meta.current.gatherData && meta.current.gatherOnMount === true) { // Get error to determine if we even want to validateAsync this.debouncedGatherInfo(name); } if (emit) this.emit('field', name); // Special event when fields value changes ( this if first time so its technically a change to initial value) if (emit) this.emit('field-value', name); // Specifically did NOT call field-modified here } }, { key: "validated", value: function validated(name, res) { debug("Setting ".concat(name, "'s error to ").concat(res, " with ").concat(this.state.validating, " validations left")); ObjectMap.set(this.state.errors, name, res); // Remember to update valid this.updateValid(); // Clear out validating this.validationRequests["delete"](name); // If we are not still validating, and we were submitting, then submit form // If we are async validating then dont submit yet if (this.state.validating > 0) { debug("Still validating ".concat(this.state.validating, " others so just update state.")); this.emit('field', name); return; } // If we were submitting if (this.state.submitting) { // Check validity and perform submission if valid if (this.valid()) { debug('Submit', this.state); this.emit('field', name); this.emit('submit'); } else { debug('Fail', this.state); if (this.options.current.focusOnInvalid) { this.focusFirstError(); } if (this.options.current.scrollOnInvalid) { this.scrollToFirstError(); } this.emit('field', name); this.emit('failure'); } this.state.submitting = false; } // If we had done function if (this.done) { // Call done only if valid if (this.valid()) this.done(); // Then always clear this.done = undefined; } // Always update this.emit('field', name); } }, { key: "gathered", value: function gathered(name, res) { debug("Setting ".concat(name, "'s data to ").concat(res, " with ").concat(this.state.gathering, " gatherers left")); ObjectMap.set(this.state.data, name, res); // Clear out gathering this.dataRequests["delete"](name); // Always update this.emit('field', name); this.emit('field-value', name); } }, { key: "gatheredError", value: function gatheredError(name, err) { debug("Setting ".concat(name, "'s error to ").concat(err, " with ").concat(this.state.gathering, " gatherers left")); ObjectMap.set(this.state.errors, name, err); // Clear out gathering this.dataRequests["delete"](name); // Remember to update valid this.updateValid(); // Always update this.emit('field', name); this.emit('field-value', name); } }, { key: "validateAsync", value: function validateAsync(name) { var _this$fieldsMap$get6, _this3 = this; debug('VALIDATING ASYNC', name); // Get meta for field var meta = (_this$fieldsMap$get6 = this.fieldsMap.get(name)) === null || _this$fieldsMap$get6 === void 0 ? void 0 : _this$fieldsMap$get6.current; // Get the value var value = this.getValue(name); if (meta && meta.asyncValidate) { this.state.validating = this.state.validating + 1; var uuid = uuidv4(); debug('REQUEST', uuid); this.validationRequests.set(name, { uuid: uuid, value: value }); // Because we may have been debounced need to update field here this.emit('field', name); meta.asyncValidate(value, this.state.values).then(function (res) { _this3.state.validating = _this3.state.validating - 1; var stale = _this3.validationRequests.get(name).uuid !== uuid; // What in the hell is invalid and why do I need it?? // 1. User types ddddd ( 5 inputs so we pass sync validation ) // 2. Because there is no sync validation async will trigger to validate username // 3. While that occurs, user starts to Backspace the ddddd // 4. The second user backspaces, sync has error so async never "re-occurs" // 5. the sync request made on step 2 completes // 6. It wipes out sync error var invalid = _this3.validationRequests.get(name).value !== _this3.getValue(name); if (!stale && !invalid) { debug('FINISH', uuid); _this3.validated(name, res); } else { debug("".concat(stale ? 'STALE' : 'INVALID', " THEN"), uuid, value, _this3.getValue(name)); } })["catch"](function (err) { _this3.state.validating = _this3.state.validating - 1; var stale = _this3.validationRequests.get(name).uuid !== uuid; var invalid = _this3.validationRequests.get(name).value !== _this3.getValue(name); if (!stale && !invalid) { debug('FINISH', uuid); _this3.validated(name, err.message); } else { debug("".concat(stale ? 'STALE' : 'INVALID', " THEN"), uuid, value, _this3.getValue(name)); } }); } } }, { key: "gatherData", value: function gatherData(name) { var _this$fieldsMap$get7, _this4 = this; debug('EXECUTING INFO ASYNC', name); // Get meta for field var meta = (_this$fieldsMap$get7 = this.fieldsMap.get(name)) === null || _this$fieldsMap$get7 === void 0 ? void 0 : _this$fieldsMap$get7.current; // Get the value var value = this.getValue(name); if (meta && meta.gatherData) { this.state.gathering = this.state.gathering + 1; var uuid = uuidv4(); debug('DATA REQUEST', uuid); this.dataRequests.set(name, { uuid: uuid, value: value }); // Because we may have been debounced need to update field here this.emit('field', name); meta.gatherData(value, this.state).then(function (res) { _this4.state.gathering = _this4.state.gathering - 1; var stale = _this4.dataRequests.get(name).uuid !== uuid; // What in the hell is invalid and why do I need it?? // because the value can be outdated var invalid = _this4.dataRequests.get(name).value !== _this4.getValue(name); if (!stale && !invalid) { debug('DATA FINISH', uuid); _this4.gathered(name, res); } else { debug("".concat(stale ? 'STALE' : 'INVALID', " THEN"), uuid, value, _this4.getValue(name)); } })["catch"](function (err) { _this4.state.gathering = _this4.state.gathering - 1; var stale = _this4.dataRequests.get(name).uuid !== uuid; var invalid = _this4.dataRequests.get(name).value !== _this4.getValue(name); if (!stale && !invalid) { debug('DATA FINISH', uuid); _this4.gatheredError(name, err.message); } else { debug("".concat(stale ? 'STALE' : 'INVALID', " THEN"), uuid, value, _this4.getValue(name)); } }); } } }, { key: "reset", value: function reset() { var _ref3, _ref4, _this$disabled; var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; debug('----------------------------- Resetting Form -----------------------------'); // There are cases where we dont want to blow away all the form values if (this.options.current.resetOnlyOnscreen) { var _this$options$current2; debug('Resetting only onscreen inputs'); this.state.initialValues = (_this$options$current2 = this.options.current.initialValues) !== null && _this$options$current2 !== void 0 ? _this$options$current2 : {}; this.fieldsMap.forEach(function (fieldMeta) { fieldMeta.current.fieldApi.reset({ resetValue: resetValues }); }); this.emit('reset'); return; } var values = options.values, _options$resetValues = options.resetValues, resetValues = _options$resetValues === void 0 ? true : _options$resetValues; this.state = { pristine: true, dirty: false, submitted: false, invalid: false, valid: true, submitting: false, validating: 0, gathering: 0, values: resetValues ? {} : this.state.values, errors: {}, touched: {}, maskedValues: resetValues ? {} : this.state.maskedValues, dirt: {}, focused: {}, modified: {}, data: {}, initialValues: (_ref3 = values !== null && values !== void 0 ? values : this.options.current.initialValues) !== null && _ref3 !== void 0 ? _ref3 : {}, disabled: (_ref4 = (_this$disabled = this.disabled) !== null && _this$disabled !== void 0 ? _this$disabled : this.options.current.disabled) !== null && _ref4 !== void 0 ? _ref4 : false, memory: {} }; // Two itterations, one for array fields then a second one for non array fields // Why? an array field reset will blow away any fields under it anyways :) so why waste time calling reset // STEP1: Array field reset this.fieldsMap.forEach(function (fieldMeta) { if (fieldMeta.current.arrayField) { debug("Resetting the array field, ".concat(fieldMeta.current.name)); fieldMeta.current.fieldApi.reset({ resetValue: resetValues }); } }); // STEP