@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
JavaScript
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