UNPKG

mobx-react-form

Version:
1,199 lines (1,181 loc) 121 kB
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('mobx'), require('lodash')) : typeof define === 'function' && define.amd ? define(['exports', 'mobx', 'lodash'], factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.MobxReactForm = {}, global.mobx, global._)); })(this, (function (exports, mobx, lodash) { 'use strict'; /** * ArrayMap: an ordered key-value collection backed by an observable array. * * Exposes the full ObservableMap-compatible API (get, set, has, delete, * merge, forEach, keys, values, entries, intercept, etc.) while * maintaining insertion order via an internal observable array. * * The observable array backing allows array-like operations such as * `move(fromIndex, toIndex)` which directly re-orders entries via * splice — fully MobX reactive. */ class ArrayMap { /** Duck-typing marker for utility code */ _isArrayMap = true; /** Internal observable array: entries stored as [key, value] pairs */ _entries = mobx.observable.array([]); constructor(entries) { if (entries && entries.length > 0) { this._entries.replace(entries); } } // ------------------------------------------------------------------ // ObservableMap-compatible API // ------------------------------------------------------------------ get size() { return this._entries.length; } get(key) { const entry = this._entries.find(([k]) => k === key); return entry ? entry[1] : undefined; } set(key, value) { const existing = this._entries.find(([k]) => k === key); if (existing) { existing[1] = value; } else { this._entries.push([key, value]); } return this; } has(key) { return this._entries.some(([k]) => k === key); } delete(key) { const index = this._entries.findIndex(([k]) => k === key); if (index !== -1) { this._entries.splice(index, 1); return true; } return false; } clear() { this._entries.clear(); } keys() { return this._entries.map(([k]) => k)[Symbol.iterator](); } values() { return this._entries.map(([, v]) => v)[Symbol.iterator](); } entries() { return this._entries[Symbol.iterator](); } forEach(callbackfn, thisArg) { this._entries.forEach(([key, value]) => { callbackfn.call(thisArg, value, key, this); }); } toJSON() { const result = {}; this._entries.forEach(([key, value]) => { result[String(key)] = value; }); return result; } toString() { return `[object ArrayMap]`; } merge(other) { if (other && typeof other === "object") { if (other.forEach && typeof other.forEach === "function") { // ObservableMap or Map-like other.forEach((value, key) => { this.set(key, value); }); } else if (Array.isArray(other)) { // Array of [key, value] pairs other.forEach(([key, value]) => { this.set(key, value); }); } else { // Plain object { key: value } Object.keys(other).forEach((key) => { this.set(key, other[key]); }); } } return this; } replace(values) { this.clear(); return this.merge(values); } get intercept() { return this._entries.intercept; } [Symbol.iterator]() { return this._entries[Symbol.iterator](); } // ------------------------------------------------------------------ // Array-specific methods // ------------------------------------------------------------------ /** * Move an entry from `fromIndex` to `toIndex`. * * Directly splices the internal observable array, so MobX * tracks the change as a single array splice operation. */ move(fromIndex, toIndex) { mobx.runInAction(() => { if (fromIndex < 0 || fromIndex >= this._entries.length) return; if (toIndex < 0 || toIndex >= this._entries.length) return; if (fromIndex === toIndex) return; const [entry] = this._entries.splice(fromIndex, 1); this._entries.splice(toIndex, 0, entry); }); } /** * Expose the underlying observable array for direct MobX * observation (e.g. `observe(arr, cb)` from mobx). */ toArray() { return this._entries; } } var FieldPropsEnum; (function (FieldPropsEnum) { FieldPropsEnum["key"] = "key"; FieldPropsEnum["id"] = "id"; FieldPropsEnum["path"] = "path"; FieldPropsEnum["name"] = "name"; FieldPropsEnum["fields"] = "fields"; FieldPropsEnum["ref"] = "ref"; FieldPropsEnum["type"] = "type"; FieldPropsEnum["computed"] = "computed"; FieldPropsEnum["value"] = "value"; FieldPropsEnum["initial"] = "initial"; FieldPropsEnum["default"] = "default"; FieldPropsEnum["checked"] = "checked"; FieldPropsEnum["label"] = "label"; FieldPropsEnum["placeholder"] = "placeholder"; FieldPropsEnum["error"] = "error"; FieldPropsEnum["validatedWith"] = "validatedWith"; FieldPropsEnum["validators"] = "validators"; FieldPropsEnum["rules"] = "rules"; FieldPropsEnum["related"] = "related"; FieldPropsEnum["options"] = "options"; FieldPropsEnum["extra"] = "extra"; FieldPropsEnum["bindings"] = "bindings"; FieldPropsEnum["hooks"] = "hooks"; FieldPropsEnum["handlers"] = "handlers"; FieldPropsEnum["converter"] = "converter"; FieldPropsEnum["input"] = "input"; FieldPropsEnum["output"] = "output"; FieldPropsEnum["interceptors"] = "interceptors"; FieldPropsEnum["observers"] = "observers"; // computed FieldPropsEnum["disabled"] = "disabled"; FieldPropsEnum["deleted"] = "deleted"; FieldPropsEnum["blurred"] = "blurred"; FieldPropsEnum["validating"] = "validating"; FieldPropsEnum["submitting"] = "submitting"; FieldPropsEnum["clearing"] = "clearing"; FieldPropsEnum["resetting"] = "resetting"; FieldPropsEnum["changed"] = "changed"; FieldPropsEnum["touched"] = "touched"; FieldPropsEnum["focused"] = "focused"; FieldPropsEnum["isEmpty"] = "isEmpty"; FieldPropsEnum["isDefault"] = "isDefault"; FieldPropsEnum["isPristine"] = "isPristine"; FieldPropsEnum["isDirty"] = "isDirty"; FieldPropsEnum["isValid"] = "isValid"; FieldPropsEnum["hasError"] = "hasError"; // handlers FieldPropsEnum["onInit"] = "onInit"; FieldPropsEnum["onSync"] = "onSync"; FieldPropsEnum["onChange"] = "onChange"; FieldPropsEnum["onBlur"] = "onBlur"; FieldPropsEnum["onFocus"] = "onFocus"; FieldPropsEnum["onToggle"] = "onToggle"; FieldPropsEnum["onDrop"] = "onDrop"; FieldPropsEnum["onSubmit"] = "onSubmit"; FieldPropsEnum["onReset"] = "onReset"; FieldPropsEnum["onClear"] = "onClear"; FieldPropsEnum["onAdd"] = "onAdd"; FieldPropsEnum["onDel"] = "onDel"; FieldPropsEnum["autoFocus"] = "autoFocus"; FieldPropsEnum["inputMode"] = "inputMode"; FieldPropsEnum["onKeyDown"] = "onKeyDown"; FieldPropsEnum["onKeyUp"] = "onKeyUp"; FieldPropsEnum["class"] = "class"; FieldPropsEnum["nullable"] = "nullable"; FieldPropsEnum["autoComplete"] = "autoComplete"; })(FieldPropsEnum || (FieldPropsEnum = {})); var AllowedFieldPropsTypes; (function (AllowedFieldPropsTypes) { AllowedFieldPropsTypes["computed"] = "computed"; AllowedFieldPropsTypes["observable"] = "observable"; AllowedFieldPropsTypes["editable"] = "editable"; AllowedFieldPropsTypes["all"] = "all"; })(AllowedFieldPropsTypes || (AllowedFieldPropsTypes = {})); var FieldPropsOccurrence; (function (FieldPropsOccurrence) { FieldPropsOccurrence["some"] = "some"; FieldPropsOccurrence["every"] = "every"; })(FieldPropsOccurrence || (FieldPropsOccurrence = {})); var SeparatedPropsMode; (function (SeparatedPropsMode) { SeparatedPropsMode["computed"] = "computed"; SeparatedPropsMode["values"] = "values"; SeparatedPropsMode["labels"] = "labels"; SeparatedPropsMode["placeholders"] = "placeholders"; SeparatedPropsMode["defaults"] = "defaults"; SeparatedPropsMode["initials"] = "initials"; SeparatedPropsMode["disabled"] = "disabled"; SeparatedPropsMode["deleted"] = "deleted"; SeparatedPropsMode["types"] = "types"; SeparatedPropsMode["related"] = "related"; SeparatedPropsMode["rules"] = "rules"; SeparatedPropsMode["options"] = "options"; SeparatedPropsMode["bindings"] = "bindings"; SeparatedPropsMode["extra"] = "extra"; SeparatedPropsMode["hooks"] = "hooks"; SeparatedPropsMode["handlers"] = "handlers"; SeparatedPropsMode["validatedWith"] = "validatedWith"; SeparatedPropsMode["validators"] = "validators"; SeparatedPropsMode["observers"] = "observers"; SeparatedPropsMode["interceptors"] = "interceptors"; SeparatedPropsMode["converters"] = "converters"; SeparatedPropsMode["input"] = "input"; SeparatedPropsMode["output"] = "output"; SeparatedPropsMode["autoFocus"] = "autoFocus"; SeparatedPropsMode["inputMode"] = "inputMode"; SeparatedPropsMode["refs"] = "refs"; SeparatedPropsMode["classes"] = "classes"; SeparatedPropsMode["nullable"] = "nullable"; SeparatedPropsMode["autoComplete"] = "autoComplete"; })(SeparatedPropsMode || (SeparatedPropsMode = {})); const props = { editable: [ FieldPropsEnum.type, FieldPropsEnum.value, FieldPropsEnum.initial, FieldPropsEnum.default, FieldPropsEnum.label, FieldPropsEnum.placeholder, FieldPropsEnum.related, FieldPropsEnum.options, FieldPropsEnum.extra, FieldPropsEnum.bindings, FieldPropsEnum.hooks, FieldPropsEnum.handlers, FieldPropsEnum.deleted, FieldPropsEnum.disabled, FieldPropsEnum.autoFocus, FieldPropsEnum.inputMode, FieldPropsEnum.ref, FieldPropsEnum.nullable, FieldPropsEnum.autoComplete, ], handlers: [ FieldPropsEnum.onChange, FieldPropsEnum.onToggle, FieldPropsEnum.onFocus, FieldPropsEnum.onBlur, FieldPropsEnum.onDrop, FieldPropsEnum.onSubmit, FieldPropsEnum.onReset, FieldPropsEnum.onClear, FieldPropsEnum.onAdd, FieldPropsEnum.onDel, ], computed: [ FieldPropsEnum.error, FieldPropsEnum.hasError, FieldPropsEnum.isValid, FieldPropsEnum.isDirty, FieldPropsEnum.isPristine, FieldPropsEnum.isDefault, FieldPropsEnum.isEmpty, FieldPropsEnum.focused, FieldPropsEnum.touched, FieldPropsEnum.changed, FieldPropsEnum.validating, FieldPropsEnum.submitting, FieldPropsEnum.resetting, FieldPropsEnum.clearing, FieldPropsEnum.blurred, FieldPropsEnum.deleted, FieldPropsEnum.disabled, ], separated: [ SeparatedPropsMode.computed, SeparatedPropsMode.values, SeparatedPropsMode.labels, SeparatedPropsMode.placeholders, SeparatedPropsMode.defaults, SeparatedPropsMode.initials, SeparatedPropsMode.disabled, SeparatedPropsMode.deleted, SeparatedPropsMode.types, SeparatedPropsMode.related, SeparatedPropsMode.rules, SeparatedPropsMode.options, SeparatedPropsMode.bindings, SeparatedPropsMode.extra, SeparatedPropsMode.hooks, SeparatedPropsMode.handlers, SeparatedPropsMode.validatedWith, SeparatedPropsMode.validators, SeparatedPropsMode.observers, SeparatedPropsMode.interceptors, SeparatedPropsMode.converters, SeparatedPropsMode.input, SeparatedPropsMode.output, SeparatedPropsMode.autoFocus, SeparatedPropsMode.inputMode, SeparatedPropsMode.refs, SeparatedPropsMode.classes, SeparatedPropsMode.nullable, SeparatedPropsMode.autoComplete, ], functions: [ FieldPropsEnum.computed, FieldPropsEnum.observers, FieldPropsEnum.interceptors, FieldPropsEnum.converter, FieldPropsEnum.input, FieldPropsEnum.output, ], validation: [ FieldPropsEnum.rules, FieldPropsEnum.validators, FieldPropsEnum.validatedWith, ], occurrences: { isDirty: FieldPropsOccurrence.some, isPristine: FieldPropsOccurrence.every, isDefault: FieldPropsOccurrence.every, isValid: FieldPropsOccurrence.every, isEmpty: FieldPropsOccurrence.every, hasError: FieldPropsOccurrence.some, focused: FieldPropsOccurrence.some, blurred: FieldPropsOccurrence.some, touched: FieldPropsOccurrence.some, deleted: FieldPropsOccurrence.every, disabled: FieldPropsOccurrence.every, clearing: FieldPropsOccurrence.every, resetting: FieldPropsOccurrence.every, }, }; const getObservableMapValues = (fields) => { // ArrayMap duck-typing if (fields && fields._isArrayMap) { const result = []; fields.forEach((value) => result.push(value)); return result; } return mobx.values(fields); }; const getObservableMapKeys = (fields) => { // ArrayMap duck-typing if (fields && fields._isArrayMap) { return Array.from(fields.keys()); } return mobx.keys(fields); }; const checkObserveItem = (change) => ({ key, to, type, exec }) => change.type === type && change.name === key && change.newValue === to && exec.apply(change, [change]); const checkObserve = (collection) => (change) => collection.map(checkObserveItem(change)); const checkPropOccurrence = ({ type, data }) => { let $check; switch (type) { case FieldPropsOccurrence.some: $check = ($data) => $data.some(Boolean); break; case FieldPropsOccurrence.every: $check = ($data) => $data.every(Boolean); break; default: throw new Error('Occurrence not found for specified prop'); } return $check(data); }; const hasProps = ($type, $data) => { let $props; switch ($type) { case AllowedFieldPropsTypes.computed: $props = props.computed; break; case AllowedFieldPropsTypes.observable: $props = [ FieldPropsEnum.fields, ...props.computed, ...props.editable, ]; break; case AllowedFieldPropsTypes.editable: $props = [ ...props.editable, ...props.validation, ...props.functions, ...props.handlers, ]; break; case AllowedFieldPropsTypes.all: $props = [ FieldPropsEnum.id, FieldPropsEnum.key, FieldPropsEnum.name, FieldPropsEnum.path, ...props.computed, ...props.editable, ...props.validation, ...props.functions, ...props.handlers, ]; break; default: $props = null; } return $data.filter((x) => $props.includes(x)).length > 0; }; /** Check Allowed Properties */ const allowedProps = (type, data) => { if (hasProps(type, data)) return; const $msg = "The selected property is not allowed"; throw new Error(`${$msg} (${JSON.stringify(data)})`); }; /** Throw Error if undefined Fields */ const throwError = (path, fields, msg = null) => { if (fields != null) return; const $msg = msg == null ? "The selected field is not defined" : msg; throw new Error(`${$msg} (${path})`); }; const pathToStruct = (path) => { let struct; struct = path.replace(/\.\d+($|\.)/g, "[]."); struct = struct.replace("..", "."); struct = struct.replace(/^\.+|\.+$/g, ""); return struct; }; const isArrayFromStruct = (struct, structPath) => { if (isArrayOfStrings(struct)) return !!struct .filter((s) => s.startsWith(structPath)) .find((s) => s.substring(structPath.length) === "[]") || (struct?.find((e) => e === structPath)?.endsWith('[]') ?? false); else return false; }; const hasSome = (obj, keys) => keys.some((key) => lodash.has(obj, key)); const isEmptyArray = (field) => lodash.isEmpty(field) && Array.isArray(field); const isArrayOfStrings = (struct) => Array.isArray(struct) && struct.every((s) => typeof s === 'string'); const isArrayOfObjects = (fields) => Array.isArray(fields) && fields.every((f) => lodash.isPlainObject(f)); const getKeys = (fields) => fields ? [...new Set(Object.values(fields).flatMap((values) => values ? Object.keys(values) : []))] : []; const hasUnifiedProps = ({ fields }) => !isArrayOfStrings({ fields }) && hasProps(AllowedFieldPropsTypes.editable, getKeys(fields)); const hasSeparatedProps = (initial) => hasSome(initial, props.separated) || hasSome(initial, props.validation); const allowNested = (field, strictProps) => field !== null && typeof field === 'object' && !(field instanceof Date) && !lodash.has(field, FieldPropsEnum.fields) && !lodash.has(field, FieldPropsEnum.class) && (!hasSome(field, [ ...props.editable, ...props.handlers, ...props.validation, ...props.functions, ]) || strictProps); const parseIntKeys = (fields) => Array.from(getObservableMapKeys(fields)).map(Number); const hasIntKeys = (fields) => parseIntKeys(fields).every((x) => Number.isInteger(x)); const maxKey = (fields) => { const keys = parseIntKeys(fields); const maxVal = keys.length ? Math.max(...keys) : undefined; return maxVal === void 0 ? 0 : maxVal + 1; }; let _idCounter = 0; const _localUniqueId = (prefix) => `${prefix}${++_idCounter}`; const uniqueId = (field) => _localUniqueId([field.path.replace(/\./g, "-"), "--"].join("")); const isEvent = (obj) => { if (obj == null || typeof Event === "undefined") return false; return obj instanceof Event || obj.target != null; }; const hasFiles = ($) => $.target.files && $.target.files.length !== 0; const isBool = ($, val) => typeof val === 'boolean' && typeof $.target.checked === 'boolean'; const $try = (...args) => { for (const val of args) { if (val !== undefined) return val; } return undefined; }; const defaultValue = ({ type = undefined, value = undefined, nullable = undefined, isEmptyArray = false, fallbackValueOption = "", }) => { if (Array.isArray(value) || isEmptyArray) return []; if (nullable || value instanceof Date || type === "date" || type === "datetime-local") return null; if (typeof value === 'number' || type === "number") return 0; if (typeof value === 'boolean' || type === "checkbox") return false; if (typeof value === 'string' || type === "file") return ""; return fallbackValueOption; }; const parsePath = (path) => { let $path = String(path ?? ''); $path = $path.replace(/\[/g, "."); $path = $path.replace(/\]/g, ""); return $path; }; const parseInput = (input, { fallbackValueOption = "", type, isEmptyArray, separated, unified, fallback }) => input($try(separated, unified, fallback, defaultValue({ fallbackValueOption, type, isEmptyArray, }))); const parseArrayProp = (val, prop, removeNullishValuesInArrays) => { const values = Object.values(val); const isValProp = [ FieldPropsEnum.value, FieldPropsEnum.initial, FieldPropsEnum.default, ].includes(prop); if (removeNullishValuesInArrays && isValProp) { return values.filter(v => v !== null && v !== undefined && v !== ""); } return values; }; const parseCheckArray = (field, value, prop, removeNullishValuesInArrays) => { if (field.incremental && value !== null && typeof value === 'object' && lodash.isEmpty(value)) return []; return field.hasIncrementalKeys ? parseArrayProp(value, prop, removeNullishValuesInArrays) : value; }; const parseCheckOutput = (field, prop, retrieveNullifiedEmptyStrings = false) => { if (prop === FieldPropsEnum.value || prop.startsWith("value.")) { const base = field.$output ? field.$output(field[FieldPropsEnum.value]) : field[FieldPropsEnum.value]; const value = prop.startsWith("value.") ? lodash.get(base, prop.substring(6)) : base; if (typeof value === 'string' && lodash.isEmpty(value) && retrieveNullifiedEmptyStrings) return null; return value; } return field[prop]; }; const defineFieldsFromStruct = (struct, add = false) => struct.reduceRight(($, name) => { const obj = {}; if (name.endsWith("[]")) { const val = add ? [$] : []; obj[name.replace(/\[\]$/, "")] = val; return obj; } // no brakets const prev = struct[struct.indexOf(name) - 1]; const stop = !!prev && prev.endsWith("[]") && struct[struct.length - 1] === name; if (!add && stop) return obj; obj[name] = $; return obj; }, {}); const handleFieldsArrayOfStrings = ($fields, add = false) => { let fields = $fields; // handle array with field struct (strings) if (isArrayOfStrings(fields)) { fields = lodash.transform(fields, ($obj, $) => { const pathStruct = $.split("."); // as array of strings (with empty values) if (!pathStruct.length) return Object.assign($obj, { [$]: "" }); // define flat or nested fields from pathStruct return lodash.merge($obj, defineFieldsFromStruct(pathStruct, add)); }, {}); } return fields; }; const handleFieldsArrayOfObjects = ($fields) => { let fields = $fields; // handle array of objects (with unified props) if (isArrayOfObjects(fields)) { fields = lodash.transform(fields, ($obj, field) => { if (hasUnifiedProps({ fields: { field } }) && !lodash.has(field, FieldPropsEnum.name)) return undefined; return Object.assign($obj, { [field.name]: field }); }, {}); } return fields; }; const handleFieldsNested = (fields, strictProps = true) => lodash.transform(fields, (obj, field, key) => { if (allowNested(field, strictProps)) { // define nested field return Object.assign(obj, { [key]: { fields: isEmptyArray(field) ? [] : handleFieldsNested(field), }, }); } return Object.assign(obj, { [key]: field }); }, {}); /* mapNestedValuesToUnifiedValues FROM: { street: '123 Fake St.', zip: '12345', } TO: [{ name: 'street' value: '123 Fake St.', }, { name: 'zip' value: '12345', }] */ const mapNestedValuesToUnifiedValues = (data) => lodash.isPlainObject(data) ? Object.entries(data).map(([name, value]) => ({ value, name })) : undefined; /* reduceValuesToUnifiedFields FROM: { name: 'fatty', address: { street: '123 Fake St.', zip: '12345', }, }; TO: { name: { value: 'fatty', fields: undefined }, address: { value: { street: '123 Fake St.', zip: '12345' }, fields: [ ... ] }, }; */ const reduceValuesToUnifiedFields = (values) => lodash.transform(values, (obj, value, key) => Object.assign(obj, { [key]: { value, fields: mapNestedValuesToUnifiedValues(value), }, }), {}); /* Fallback Unified Props to Separated Mode */ const handleFieldsPropsFallback = (fields, initial, fallback) => { if (!lodash.has(initial, SeparatedPropsMode.values)) return fields; // if the 'values' object is passed in constructor // then update the fields definitions let { values } = initial; if (hasUnifiedProps({ fields: initial.fields })) { values = reduceValuesToUnifiedFields(values); } return lodash.merge(fields, lodash.transform(values, (result, v, k) => { if (Array.isArray(fields[k])) result[k] = v; if (!(k in fields) && (!isNaN(Number(k)) || fallback)) result[k] = v; }, {})); }; const mergeSchemaDefaults = (fields, validator) => { if (validator) { const schema = lodash.get(validator.plugins, "svk.config.schema"); if (lodash.isEmpty(fields) && schema && !!schema.properties) { lodash.each(schema.properties, (prop, key) => { lodash.set(fields, key, { value: prop.default, label: prop.title, }); }); } } return fields; }; const prepareFieldsData = (initial, strictProps = true, fallback = true) => { let fields = lodash.merge(handleFieldsArrayOfStrings(initial.fields, false), handleFieldsArrayOfStrings(initial.struct, false)); fields = handleFieldsArrayOfObjects(fields); fields = handleFieldsPropsFallback(fields, initial, fallback); fields = handleFieldsNested(fields, strictProps); return fields; }; const pathToFieldsTree = (struct, path, n = 0, add = false) => { const $struct = (Array.isArray(struct) ? struct : Object.values(struct)).filter((item) => typeof item === 'string'); const structPath = pathToStruct(path); const structArray = $struct.filter((item) => item.startsWith(structPath)); const $tree = handleFieldsArrayOfStrings(structArray, add); const $structPath = structPath.replace(/\[\]/g, `[${n}]`); const fields = handleFieldsNested(lodash.get($tree, $structPath)); // fix issues #614 & #615 $struct.length && $struct .filter(s => s.startsWith(path + '[]')) .map(s => s.substring((path + '[].').length)) .filter(s => s.endsWith('[]')) .map(s => s.substring(0, s.length - 2)) .forEach(s => { const ss = s.split('.'); let t = fields[0]?.fields; for (let i = 0; i < ss.length; i++) { t = t?.[ss[i]]?.[FieldPropsEnum.fields]; if (!t) break; } if (t) delete t[0]; }); return fields; }; var OptionsEnum; (function (OptionsEnum) { OptionsEnum["uniqueId"] = "uniqueId"; OptionsEnum["fallback"] = "fallback"; OptionsEnum["fallbackValue"] = "fallbackValue"; OptionsEnum["defaultGenericError"] = "defaultGenericError"; OptionsEnum["submitThrowsError"] = "submitThrowsError"; OptionsEnum["showErrorsOnInit"] = "showErrorsOnInit"; OptionsEnum["showErrorsOnSubmit"] = "showErrorsOnSubmit"; OptionsEnum["showErrorsOnBlur"] = "showErrorsOnBlur"; OptionsEnum["showErrorsOnChange"] = "showErrorsOnChange"; OptionsEnum["showErrorsOnClear"] = "showErrorsOnClear"; OptionsEnum["showErrorsOnReset"] = "showErrorsOnReset"; OptionsEnum["validateOnInit"] = "validateOnInit"; OptionsEnum["validateOnSubmit"] = "validateOnSubmit"; OptionsEnum["validateOnBlur"] = "validateOnBlur"; OptionsEnum["validateOnChange"] = "validateOnChange"; OptionsEnum["validateOnChangeAfterInitialBlur"] = "validateOnChangeAfterInitialBlur"; OptionsEnum["validateOnChangeAfterSubmit"] = "validateOnChangeAfterSubmit"; OptionsEnum["validateDisabledFields"] = "validateDisabledFields"; OptionsEnum["validateDeletedFields"] = "validateDeletedFields"; OptionsEnum["validatePristineFields"] = "validatePristineFields"; OptionsEnum["validateTrimmedValue"] = "validateTrimmedValue"; OptionsEnum["validateOnClear"] = "validateOnClear"; OptionsEnum["validateOnReset"] = "validateOnReset"; OptionsEnum["strictSet"] = "strictSet"; OptionsEnum["strictUpdate"] = "strictUpdate"; OptionsEnum["strictDelete"] = "strictDelete"; OptionsEnum["strictSelect"] = "strictSelect"; OptionsEnum["softDelete"] = "softDelete"; OptionsEnum["retrieveOnlyDirtyFieldsValues"] = "retrieveOnlyDirtyFieldsValues"; OptionsEnum["retrieveOnlyEnabledFieldsValues"] = "retrieveOnlyEnabledFieldsValues"; OptionsEnum["retrieveOnlyEnabledFieldsErrors"] = "retrieveOnlyEnabledFieldsErrors"; OptionsEnum["retrieveNullifiedEmptyStrings"] = "retrieveNullifiedEmptyStrings"; OptionsEnum["removeNullishValuesInArrays"] = "removeNullishValuesInArrays"; OptionsEnum["preserveDeletedFieldsValues"] = "preserveDeletedFieldsValues"; OptionsEnum["autoTrimValue"] = "autoTrimValue"; OptionsEnum["autoParseNumbers"] = "autoParseNumbers"; OptionsEnum["validationDebounceWait"] = "validationDebounceWait"; OptionsEnum["validationDebounceOptions"] = "validationDebounceOptions"; OptionsEnum["stopValidationOnError"] = "stopValidationOnError"; OptionsEnum["validationPluginsOrder"] = "validationPluginsOrder"; OptionsEnum["resetValidationBeforeValidate"] = "resetValidationBeforeValidate"; OptionsEnum["applyInputConverterOnInit"] = "applyInputConverterOnInit"; OptionsEnum["applyInputConverterOnSet"] = "applyInputConverterOnSet"; OptionsEnum["applyInputConverterOnUpdate"] = "applyInputConverterOnUpdate"; OptionsEnum["bubbleUpErrorMessages"] = "bubbleUpErrorMessages"; })(OptionsEnum || (OptionsEnum = {})); var ValidationHooks; (function (ValidationHooks) { ValidationHooks["onSuccess"] = "onSuccess"; ValidationHooks["onError"] = "onError"; })(ValidationHooks || (ValidationHooks = {})); class Base { noop = () => { }; state; fields = new ArrayMap(); path; $submitted = 0; $submitting = false; $validated = 0; $validating = false; $clearing = false; $resetting = false; $touched = false; $changed = 0; $hooks = {}; $handlers = {}; constructor() { mobx.makeObservable(this, { $submitted: mobx.observable, $submitting: mobx.observable, $validated: mobx.observable, $validating: mobx.observable, $clearing: mobx.observable, $resetting: mobx.observable, $touched: mobx.observable, $changed: mobx.observable, $hooks: mobx.observable, $handlers: mobx.observable, changed: mobx.computed, submitted: mobx.computed, submitting: mobx.computed, validated: mobx.computed, validating: mobx.computed, clearing: mobx.computed, resetting: mobx.computed, hasIncrementalKeys: mobx.computed, hasNestedFields: mobx.computed, size: mobx.computed, // initialization initField: mobx.action, // actions submit: mobx.action, deepUpdate: mobx.action, set: mobx.action, add: mobx.action, del: mobx.action, }); } execHook = (name, fallback = {}) => $try(fallback[name], this.$hooks[name], this.noop).apply(this, [this]); execHandler = (name, args, fallback = undefined, hook = null, execHook = true) => [ $try(this.$handlers[name] && this.$handlers[name].apply(this, [this]), fallback, this.noop).apply(this, [...args]), execHook && this.execHook(hook || name), ]; get resetting() { return this.hasNestedFields ? this.check(FieldPropsEnum.resetting, true) : this.$resetting; } get clearing() { return this.hasNestedFields ? this.check(FieldPropsEnum.clearing, true) : this.$clearing; } get submitted() { return mobx.toJS(this.$submitted); } get submitting() { return mobx.toJS(this.$submitting); } get validated() { return mobx.toJS(this.$validated); } get validating() { return mobx.toJS(this.$validating); } get hasIncrementalKeys() { return !!this.fields.size && hasIntKeys(this.fields); } get hasNestedFields() { return this.fields.size !== 0; } get size() { return this.fields.size; } get changed() { return this.path != null && this.hasNestedFields ? this.reduce((acc, field) => acc + field.changed, 0) + this.$changed : this.$changed; } /** Interceptor */ intercept = (opt) => this.MOBXEvent(typeof opt === "function" ? { type: "interceptor", call: opt } : { type: "interceptor", ...opt }); /** Observer */ observe = (opt) => this.MOBXEvent(typeof opt === "function" ? { type: "observer", call: opt } : { type: "observer", ...opt }); /** Event Handler: On Clear */ onClear = (...args) => this.execHandler(FieldPropsEnum.onClear, args, (e) => { isEvent(e) && e.preventDefault(); this.clear(true, false); }); /** Event Handler: On Reset */ onReset = (...args) => this.execHandler(FieldPropsEnum.onReset, args, (e) => { isEvent(e) && e.preventDefault(); this.reset(true, false); }); /** Event Handler: On Submit */ onSubmit = (...args) => this.execHandler(FieldPropsEnum.onSubmit, args, (e, o = {}) => { isEvent(e) && e.preventDefault(); this.submit(o); }, null, false); /** Event Handler: On Add */ onAdd = (...args) => this.execHandler(FieldPropsEnum.onAdd, args, (e, val) => { isEvent(e) && e.preventDefault(); this.add(isEvent(val) ? null : val, false); }); /** Event Handler: On Del */ onDel = (...args) => this.execHandler(FieldPropsEnum.onDel, args, (e, path) => { isEvent(e) && e.preventDefault(); this.del(isEvent(path) ? this.path : path, false); }); /****************************************************************** Initializer */ initFields(initial, update = false) { const fallback = this.state.options.get(OptionsEnum.fallback); const $path = (key) => [this.path, key].join(".").replace(/^\.+/, ""); let fields; fields = prepareFieldsData(initial, this.state.strict, fallback); fields = mergeSchemaDefaults(fields, this.validator); // create fields lodash.forIn(fields, (field, key) => { const path = $path(key); const $f = this.select(path, null, false); if ($f == null) { if (fallback) { this.initField(key, path, field, update); } else { const structPath = pathToStruct(path); const struct = this.state.struct(); const found = struct .filter((s) => s.startsWith(structPath)) .find((s) => s.charAt(structPath.length) === "." || s.substring(structPath.length, structPath.length + 2) === "[]" || s === structPath); if (found) this.initField(key, path, field, update); } } }); } initField(key, path, data, update = false) { const initial = this.state.get("current", "props"); const struct = pathToStruct(path); // try to get props from separated objects const _try = (prop) => { const t = lodash.get(initial[prop], struct); if ([ FieldPropsEnum.input, FieldPropsEnum.output, FieldPropsEnum.converter, ].includes(prop) && typeof t !== "function") return undefined; return t; }; const props = { $value: lodash.get(initial[SeparatedPropsMode.values], path), $computed: _try(SeparatedPropsMode.computed), $label: _try(SeparatedPropsMode.labels), $placeholder: _try(SeparatedPropsMode.placeholders), $default: _try(SeparatedPropsMode.defaults), $initial: _try(SeparatedPropsMode.initials), $disabled: _try(SeparatedPropsMode.disabled), $deleted: _try(SeparatedPropsMode.deleted), $type: _try(SeparatedPropsMode.types), $related: _try(SeparatedPropsMode.related), $rules: _try(SeparatedPropsMode.rules), $options: _try(SeparatedPropsMode.options), $bindings: _try(SeparatedPropsMode.bindings), $extra: _try(SeparatedPropsMode.extra), $hooks: _try(SeparatedPropsMode.hooks), $handlers: _try(SeparatedPropsMode.handlers), $validatedWith: _try(SeparatedPropsMode.validatedWith), $validators: _try(SeparatedPropsMode.validators), $observers: _try(SeparatedPropsMode.observers), $interceptors: _try(SeparatedPropsMode.interceptors), $converters: _try(SeparatedPropsMode.converters), $input: _try(SeparatedPropsMode.input), $output: _try(SeparatedPropsMode.output), $autoFocus: _try(SeparatedPropsMode.autoFocus), $ref: _try(SeparatedPropsMode.refs), $nullable: _try(SeparatedPropsMode.nullable), $autoComplete: _try(SeparatedPropsMode.autoComplete), }; const field = this.state.form.makeField({ key, path, struct, data, props, update, state: this.state, }, (data && data[FieldPropsEnum.class]) || _try(SeparatedPropsMode.classes)); this.fields.merge({ [key]: field }); return field; } /****************************************************************** Actions */ validate(opt, obj) { const $opt = lodash.merge(opt, { path: this.path }); return this.state.form.validator.validate($opt, obj); } /** Submit */ submit(hooks = {}, { execOnSubmitHook = true, execValidationHooks = true, validate = true, } = {}) { const execOnSubmit = () => this.execHook(FieldPropsEnum.onSubmit, hooks); const submit = execOnSubmitHook ? execOnSubmit() : undefined; this.$submitting = true; this.$submitted += 1; if (!validate || !this.state.options.get(OptionsEnum.validateOnSubmit, this)) { return Promise.resolve(submit) .then(mobx.action(() => (this.$submitting = false))) .catch(mobx.action((err) => { this.$submitting = false; throw err; })) .then(() => this); } const exec = (isValid) => isValid ? this.execHook(ValidationHooks.onSuccess, hooks) : this.execHook(ValidationHooks.onError, hooks); return this.validate({ showErrors: this.state.options.get(OptionsEnum.showErrorsOnSubmit, this), }) .then(({ isValid }) => { const handler = execValidationHooks ? exec(isValid) : undefined; if (isValid) return Promise.all([submit, handler]); const $err = this.state.options.get(OptionsEnum.defaultGenericError, this); const $throw = this.state.options.get(OptionsEnum.submitThrowsError, this); if ($throw && $err) this.invalidate(); return Promise.all([submit, handler]); }) .then(mobx.action(() => (this.$submitting = false))) .catch(mobx.action((err) => { this.$submitting = false; throw err; })) .then(() => this); } /** Check Field Computed Values */ check(prop, deep = false) { allowedProps(AllowedFieldPropsTypes.computed, [prop]); return deep ? checkPropOccurrence({ type: props.occurrences[prop], data: this.deepCheck(props.occurrences[prop], prop, this.fields), }) : this[prop]; } deepCheck(type, prop, fields) { const $fields = getObservableMapValues(fields); return lodash.transform($fields, (check, field) => { if (!field.fields.size || !Array.isArray(field.initial)) { check.push(field[prop]); } check.push(checkPropOccurrence({ data: this.deepCheck(type, prop, field.fields), type, })); return check; }, []); } firstError() { if (!this.state.options.get(OptionsEnum.bubbleUpErrorMessages, this)) return null; for (const field of getObservableMapValues(this.fields)) { if (field.error) return field.error; if (field.fields.size) { const nested = field.firstError(); if (nested) return nested; } } return null; } /** Update Field Values recurisvely OR Create Field if 'undefined' */ update(fields) { if (!lodash.isPlainObject(fields)) { throw new Error("The update() method accepts only plain objects."); } this.deepUpdate(prepareFieldsData({ fields }, this.state.strict), undefined, undefined, fields); } deepUpdate(fields, path = "", recursion = true, raw) { lodash.each(fields, (field, key) => { const $key = lodash.has(field, FieldPropsEnum.name) ? field.name : key; const $path = `${path}.${$key}`.replace(/^\.+/, ""); const strictUpdate = this.state.options.get(OptionsEnum.strictUpdate, this); const $field = this.select($path, null, strictUpdate); const $container = this.select(path, null, false) || this.state.form.select(this.path ?? '', null, false); const applyInputConverterOnUpdate = this.state.options.get(OptionsEnum.applyInputConverterOnUpdate, this); if ($field != null && field !== void 0) { if (Array.isArray($field.values())) { const n = Math.max(-1, ...Object.keys(field.fields || {}).map(Number)); getObservableMapValues($field.fields).forEach(($f) => { if (Number($f.name) > n) { $field.$changed++; $field.state.form.$changed++; $field.fields.delete($f.name); } }); } if (field?.fields) { const fallback = this.state.options.get(OptionsEnum.fallback); const x = this.state .struct() .findIndex((s) => s.startsWith($field.path.replace(/\.\d+\./, "[].") + "[]")); if (!fallback && $field.fields.size === 0 && x < 0) { $field.value = parseInput(applyInputConverterOnUpdate ? $field.$input : (val) => val, { fallbackValueOption: this.state.options.get(OptionsEnum.fallbackValue, this), separated: lodash.get(raw, $path), }); return; } } if (field === null || field.fields == null) { $field.value = parseInput(applyInputConverterOnUpdate ? $field.$input : (val) => val, { fallbackValueOption: this.state.options.get(OptionsEnum.fallbackValue, this), separated: field, }); return; } } if ($container != null && $field == null) { // get full path when using update() with select() - FIX: #179 const $newFieldPath = [this.path, $path].join(".").replace(/^\.+/, ""); // init field into the container field $container.$changed++; $container.state.form.$changed++; $container.initField($key, $newFieldPath, field, true); } else if (recursion) { if (lodash.has(field, FieldPropsEnum.fields) && field.fields != null) { // handle nested fields if defined this.deepUpdate(field.fields, $path); } else { // handle nested fields if undefined or null const $fields = pathToFieldsTree(this.state.struct(), $path); this.deepUpdate($fields, $path, false); } } }); } /** Get Fields Props */ get(prop = null, strict = true) { if (prop == null) { return this.deepGet([...props.computed, ...props.editable, ...props.validation], this.fields, strict); } allowedProps(AllowedFieldPropsTypes.all, Ar