UNPKG

@react-form-builder/core

Version:

React JSON Schema Form Builder to create complex, validated, reusable forms with no deep React knowledge required

522 lines (521 loc) 14.4 kB
import { makeAutoObservable as m, reaction as g } from "mobx"; import { cfSkipChildrenDuringFieldCollection as C } from "../../features/define/utils/integratedComponentFeatures.js"; import { dataKey as c } from "../../stores/ComponentStore.js"; import { defaultComponentState as D } from "../../stores/IComponentState.js"; import { createNonNullableContext as E } from "../createNonNullableContext.js"; import { mergeData as v } from "../data-utils.js"; import { isRecord as f } from "../isRecord.js"; import { nameObservable as A } from "../observableNaming.js"; import { SyncEvent as p } from "../SyncEvent.js"; import { uniqueId as K, isNumber as F, merge as u, isUndefined as k, isEmpty as x, camelCase as b } from "../tools.js"; import { treeForEach as d } from "../treeUtils.js"; function y(o, e) { let t = 1; const i = b(o.type), s = () => `${i}${t}`; for (; e.has(s()); ) t++; return o.key = s(), o.key; } function l(o) { return typeof o.disableReaction == "function"; } class w { /** * Constructs a new instance of the ComponentKeyChangedEventArgs class. * @param oldKey the old key. * @param newKey the new key. */ constructor(e, t) { this.oldKey = e, this.newKey = t; } oldKey; newKey; } class O { /** * An event that occurs after a component key change. */ onAfterKeyChanged = new p(); /** * An event that occurs before a component is removed from the component tree. */ onBeforeDelete = new p(); /** * Unsubscribe from all events. */ dispose() { this.onAfterKeyChanged.dispose(), this.onBeforeDelete.dispose(); } } const q = (o) => { const e = {}; return Object.entries(o.data).forEach(([t, i]) => { e[t] = i ?? ""; }), e; }; class z { #i; #e; #t; _state = {}; /** * The unique identifier. */ id; /** * The component settings. */ store; /** * The component metadata. */ model; /** * The field with the form data. */ field; /** * The parent node in the component data tree. */ parent; /** * The child nodes in the component data tree. */ children = []; /** * User defined properties of the React component. */ userDefinedProps; /** * If true, then validation is in progress. */ validating = !1; /** * Specifies the root component for the data in the component tree. **Internal use only.** */ dataRootProvider; /** * Specifies the index in the array if the component is in the component array. * This is not an index in a parent-child structure. */ index; /** * The state of the component. **Internal use only. */ componentState = D; /** * The function for getting initial data. **Internal use only.** */ getInitialData; /** * The function for updating initial data. **Internal use only.** */ setInitialData; /** * Constructor. * @param componentStore the component settings. * @param model the component metadata for the form viewer. * @param childFactory the factory function that creates {@link ComponentData} instance. * @param getFormValidationResult the function that returns a form validation results. */ constructor(e, t, i, s) { this.store = e, this.model = t, this.id = K(`${this.model.type}_`), this.#t = s, e.children?.forEach((n) => { i(n).setParent(this); }), m(this, void 0, { name: A("ComponentData", { key: e.key }) }); const r = () => g(() => this.key, (n, a) => { this.invokeOnAfterKeyChanged(this, new w(a, n)); }); this.#i = [r()]; } /** * Sets the new parent for this node. * @param newParent the new parent. */ setParent(e) { this.parent?.removeChild(this), e.addChild(this), this.parent = e; } /** * Inserts the given node after this node. * @param inserted the node to insert. */ insertAfterMe(e) { this.insert(e, "after"); } /** * Inserts the given node before this node. * @param inserted the node to insert. */ insertBeforeMe(e) { this.insert(e, "before"); } /** * @inheritDoc */ get state() { return this._state; } /** * @inheritDoc */ set state(e) { this._state = e; } /** * @returns the root component for the data in the component tree. */ get dataRoot() { return this.dataRootProvider?.dataRoot ?? this.parent?.dataRoot ?? this; } /** * @returns the initial data. */ get initialData() { return this.dataRoot.getInitialData?.(); } /** * Updates the initial data. **Internal use only.** * @param key the initial data key. * @param value the initial data value. */ updateInitialData(e, t) { this.dataRoot.setInitialData?.(e, t); } /** * @returns the key of this node (same as the key of the ComponentStore). */ get key() { return this.store.key; } /** * @returns the ComponentDataEvents object. */ get events() { return this.#e || (this.#e = new O()), this.#e; } /** * Find the node with the given key. * @param key the key to find. * @returns the node or undefined if not found. */ findByKey(e) { if (this.key === e) return this; for (let t = 0; t < this.children.length; t++) { const s = this.children[t].findByKey(e); if (s) return s; } } /** * Assigns unique keys to the items in the tree. * @param root the root of the tree to unify keys. Defaults to the root of this tree. * @returns the map of new keys to old keys. */ unifyKeys(e) { const t = /* @__PURE__ */ new Map(), i = []; return d(e, ({ key: s }) => { i.push(s); }), d(this, (s) => { const r = y(s.store, new Set(i)); t.set(r, s.key), i.push(r); }), t; } /** * Assigns unique keys to the items in the tree. */ unifyTree() { const e = /* @__PURE__ */ new Map(); d(this, ({ key: s, store: r }) => { const n = e.get(s) ?? /* @__PURE__ */ new Set(); n.add(r), e.set(s, n); }); const i = new Set(e.keys()); e.forEach((s, r) => { if (s.size <= 1) return; [...s].slice(1).forEach((a) => { const h = y(a, i); i.add(h); }); }); } /** * @returns all the fields in the tree as a map. Starts from this node. */ get fields() { const e = this.collectAllFields(/* @__PURE__ */ new Map()), t = /* @__PURE__ */ new Map(); return e.forEach((i, s) => { t.set(c(s.store), i); }), t; } /** * @returns an array of all component fields, including non-unique data keys. */ get allComponentFields() { const e = this.collectAllFields(/* @__PURE__ */ new Map()), t = []; return e.forEach((i, s) => { t.push({ dataKey: c(s.store), field: i }); }), t; } /** * @returns an array of all fields, including non-unique data keys. */ get allFields() { return this.allComponentFields.map((e) => e.field); } /** * @returns an array of all children components. */ get allChildren() { return this.collectAllChildren([]); } /** * Deletes this node from the tree. */ delete() { this.parent?.removeChild(this); const e = this.collectAllNodesAsArray([]); this.invokeOnBeforeDeleted(e), this.disposeNodes(e); } /** * @inheritDoc */ get data() { const e = { ...this.generatedData() }, t = f(this.initialData) ? { ...this.initialData } : {}; return v(e, t); } /** * @returns the generated form data. */ generatedData() { const e = {}; for (const { dataKey: t, field: i } of this.allComponentFields) if (i.storeDataInParentForm) { const s = i.value || {}; Object.keys(s).forEach((r) => e[r] = s[r]); } else e[t] = i.value; return e; } /** * @returns the object to read and modify parent data (available for array elements). */ get parentData() { if (F(this.nearestIndex)) return this.parent?.data ?? this.rootData; } /** * @returns the object to read and modify root form data. */ get rootData() { return this.root.data; } /** * @inheritDoc */ get errors() { const e = {}; for (const { dataKey: t, field: i } of this.allComponentFields) { i.error && (e[t] = i.error); const s = i.errors; s && (Array.isArray(s) ? e[t] = s : (i.storeDataInParentForm && Object.keys(s).forEach((r) => e[r] = s[r]), !i.storeDataInParentForm && Object.keys(s).length > 0 && (e[t] = s))); } return e; } /** * @inheritDoc */ set errors(e) { this.allComponentFields.forEach((t) => { const i = e[t.dataKey]; t.field.setError(i); }); } /** * @inheritDoc */ get hasErrors() { return Object.keys(this.errors).length > 0; } /** * @inheritDoc */ setAllErrors(e) { this.allFields.forEach((t) => t.error = e); } /** * @inheritDoc */ async validate() { return await this.validateForm(), this.errors; } /** * @inheritDoc */ async getValidationResult() { let e; const t = async ({ dataKey: s, field: r }) => { const n = await r.getValidationResult(); if (x(n)) return; e ??= {}; let a = e; if (!r.storeDataInParentForm) { const h = Array.isArray(n) ? [] : {}; e[s] ??= h, a = e[s]; } u(a, n); }; return await Promise.allSettled(this.allComponentFields.map(t)), (await this.#t?.())?.forEach((s) => { e ??= {}, u(e, s); }), e; } /** * @inheritDoc */ get isValidating() { return this.validating; } /** * @inheritDoc */ reset(e = !0) { e && this.clearInitialData(), this.allFields.forEach((t) => { l(t) && t.disableReaction(), t.reset(); }), this.allFields.forEach((t) => { l(t) && t.enableReaction(); }); } /** * @inheritDoc */ clear(e = !0) { e && this.clearInitialData(), this.allFields.forEach((t) => { l(t) && t.disableReaction(), t.clear(); }), this.allFields.forEach((t) => { l(t) && t.enableReaction(); }); } /** * Dispose method that releases resources used by the object. * It disposes the field and all the children objects. */ dispose() { const e = this.collectAllNodesAsArray([]); this.disposeNodes(e); } /** * @returns true if it has no parent {@link ComponentData}, false otherwise. */ get isRoot() { return !this.parent; } /** * @returns the root of the component tree. */ get root() { return this.parent ? this.parent.root : this; } /** * @returns the index in the array if the component is in the component array * (looks for the nearest index in the component hierarchy). */ get nearestIndex() { return k(this.index) ? this.parent?.nearestIndex : this.index; } async validateForm() { this.validating = !0; try { await Promise.allSettled([...this.allFields].map((i) => i.validate())); const e = await this.#t?.(); if (!e?.length) return; const t = this.allComponentFields; e.forEach((i) => { i && t.forEach(({ field: s, dataKey: r }) => { if (i[r]) return s.setError(i[r]); if (s.storeDataInParentForm) return s.setError(i); }); }); } finally { this.validating = !1; } } insert(e, t) { const i = t === "before" ? 0 : 1; if (!this.parent) throw new Error(`Cannot insert without parent. Key = ${this.key}`); e.parent?.removeChild(e), e.parent = this.parent; const s = this.parent.children, r = s.indexOf(this); if (r < 0) throw new Error(`Cannot insert not existing element into ComponentData. Key = ${this.key}`); s.splice(r + i, 0, e), this.parent.store.children ??= []; const n = this.parent.store.children, a = n.indexOf(this.store); if (a < 0) throw new Error(`Cannot insert not existing element into ComponentStore. Key = ${this.key}`); n.splice(a + i, 0, e.store), e.store.slot = this.store.slot, e.store.slotCondition = this.store.slotCondition; } /** * Disposes the nodes by calling the disposers, disposing the field, * and resetting the parent and children properties to undefined and an empty array, respectively. * @param nodes the array of ComponentData objects representing the nodes to dispose. */ disposeNodes(e) { e.forEach((t) => { t.#e?.dispose(), t.#i.forEach((i) => i()), t.field?.dispose(), t.parent = void 0, t.children = []; }); } collectAllNodesAsArray(e) { e.push(this); for (let t = 0; t < this.children.length; t++) this.children[t].collectAllNodesAsArray(e); return e; } collectAllFields(e) { if (this.field && e.set(this, this.field), this.model.isFeatureEnabled(C)) return e; for (let t = 0; t < this.children.length; t++) this.children[t].collectAllFields(e); return e; } collectAllChildren(e) { e.push(this); for (let t = 0; t < this.children.length; t++) this.children[t].collectAllChildren(e); return e; } addChild(e) { this.children.indexOf(e) < 0 && this.children.push(e), this.store.children ??= [], this.store.children.indexOf(e.store) < 0 && this.store.children.push(e.store); } removeChild(e) { const t = this.children.indexOf(e); t > -1 && this.children.splice(t, 1), this.store.children ??= []; const i = this.store.children.indexOf(e.store); i > -1 && this.store.children.splice(i, 1); } invokeOnAfterKeyChanged(e, t) { this.#e?.onAfterKeyChanged.isSubscribed && this.#e.onAfterKeyChanged.invoke(e, t), this.parent?.invokeOnAfterKeyChanged(e, t); } invokeOnBeforeDeleted(e) { this.#e?.onBeforeDelete.isSubscribed && e.forEach((t) => this.#e?.onBeforeDelete.invoke(t, void 0)), this.parent?.invokeOnBeforeDeleted(e); } clearInitialData() { const e = this.initialData; e && f(e) && Object.keys(e).forEach((t) => delete e[t]); } } const [ /** * @returns the instance of the ComponentData of the currently rendered component. */ L, /** * Context provider for the useComponentData hook. **Internal use only.** */ T ] = E("ComponentDataContext"); export { z as ComponentData, O as ComponentDataEvents, T as ComponentDataProvider, w as ComponentKeyChangedEventArgs, q as getEditableFormData, L as useComponentData }; //# sourceMappingURL=ComponentDataContext.js.map