@worktif/purei
Version:
Work TIF Material UI Theme Provider and Customization Suite for React applications with dark mode support and dynamic color schemes
562 lines • 23.9 kB
JavaScript
;
/*
* Work TIF, Raman Marozau.
* All rights reserved.
* Copyright(c), 2025-present.
*
* Business Source License 1.1
*
* Copyright (C) 2025 Raman Marozau, raman@worktif.com
* Use of this software is governed by the Business Source License included in the LICENSE file and at www.mariadb.com/bsl11.
*
* Change Date: Never
* On the date above, in accordance with the Business Source License, use of this software will be governed by the open source license specified in the LICENSE file.
* Additional Use Grant: Free for personal and non-commercial research use only.
*
*
* SPDX-License-Identifier: BUSL-1.1
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.FormProvider = exports.FormsContext = exports.FormFieldValidationType = exports.FormFieldType = void 0;
const React = __importStar(require("react"));
const react_1 = require("react");
const form_error_1 = require("./form.error");
const utils_1 = require("../../utils");
var FormFieldType;
(function (FormFieldType) {
FormFieldType["Text"] = "text";
FormFieldType["Checkbox"] = "checkbox";
})(FormFieldType || (exports.FormFieldType = FormFieldType = {}));
var FormFieldValidationType;
(function (FormFieldValidationType) {
FormFieldValidationType["MaxLength"] = "max-length";
FormFieldValidationType["MinLength"] = "min-length";
FormFieldValidationType["Alphabetically"] = "alphabetically";
FormFieldValidationType["ShouldExist"] = "should-exist";
FormFieldValidationType["Email"] = "email";
FormFieldValidationType["Password"] = "password";
FormFieldValidationType["Numerical"] = "numerical";
})(FormFieldValidationType || (exports.FormFieldValidationType = FormFieldValidationType = {}));
exports.FormsContext = (0, react_1.createContext)({
value: void 0,
setValue: (value, index) => void 0,
getFieldValue: (fieldName, index) => void 0,
setFieldValue: (fieldName, value, index) => void 0,
isError: (fieldName, index) => false,
getError: (fieldName, index) => void 0,
setFormValue: (value, isProcess, index) => void 0,
getValue: (fields) => void 0,
hasError: (index) => false,
setSchema: (schema) => void 0,
composeSchema: (schema, body) => void 0,
isSubmit: (index) => false,
setSubmit: (isSubmit) => void 0,
isFormProcess: (index) => false,
setFormProcess: (isProcess) => void 0,
setChange: (isChange, index) => void 0,
change: (index) => false,
i18nSchema: void 0,
});
/**
* FormProvider is a React functional component that manages and provides form state
* and functionality to its children. This includes form values, schemas, submission
* state, change tracking, validation, and error management.
*
* Props:
* - `children` (ReactNode): Components that will be rendered within the context of the FormProvider.
* - `i18nSchema` (any): Optional schema for internationalization-related configurations.
*
* Features and State Management:
* - Manages the form values and enables updates to individual field values or the entire form.
* - Tracks the state of form submission and whether the form is currently being processed.
* - Maintains a schema to compose and validate form input definitions.
* - Tracks form change states to determine if any fields have been modified.
* - Provides form-level and field-level error management, including error retrieval and validation.
*
* Key Methods:
* - `setChange`: Updates the change state of a specific form array item.
* - `change`: Checks if any indexed form array item has changed.
* - `isSubmit`: Determines if the form or a specific form array item has been submitted.
* - `setSubmit`: Sets the submission state for the form or a specific array item.
* - `isFormProcess`: Checks if a specific form array item is being processed.
* - `setFormProcess`: Marks an array item as processed or not.
* - `setSubmitElements`: Sets the submit state for every element in the form schema.
* - `isElementSubmit`: Checks if a specific form array element is in a submitted state.
* - `setElementSubmit`: Marks a specific form array item as submitted or not.
* - `composeSchema`: Initializes form schema and composes initial form value states.
* - `setFormValue`: Updates the form values, applies on-the-fly validation, and handles the processing state.
* - `getValue`: Retrieves specific field values or all values of an array item.
* - `setFieldValue`: Updates the value of a specific field within a form array item.
* - `hasError`: Checks if the form or a specific array item has any validation errors.
* - `getError`: Retrieves the error message for a specific field in a form array item.
*
* Provides a context for managing and sharing form state and related operations
* between components within its hierarchy. Ideal for complex forms with potentially
* dynamic fields and logic.
*
* * @todo: different forms on the single page required the categorisation of the form ssr.handler, so that the context should define an object of any state value where the property name is a form category
*/
const FormProvider = ({ children, i18nSchema }) => {
const [value, setValue] = (0, react_1.useState)(utils_1.EMPTY_ARRAY);
// @todo: schema default value by array
const [schema, setSchema] = (0, react_1.useState)([{ form: [] }]);
const [isFormSubmit, setIsFormSubmit] = (0, react_1.useState)([false]);
const [isFormProcessValue, setIsFormProcessValue] = (0, react_1.useState)([
false,
]);
const [formChange, setFormChange] = (0, react_1.useState)([false]);
(0, react_1.useEffect)(() => {
setIsFormProcessValue([false]);
setSubmitElements(false);
setSchema([{ form: [] }]);
setValue(utils_1.EMPTY_ARRAY);
return () => {
setIsFormProcessValue([false]);
setSubmitElements(false);
setSchema([{ form: [] }]);
setValue(utils_1.EMPTY_ARRAY);
};
}, []);
/**
* Defines the form item change state
* @param change
* @param index
*/
function setChange(change, index) {
setFormChange((isChange) => {
isChange[index || utils_1.EMPTY_NUMBER] = change;
return isChange;
});
}
/**
* Whether the form item has changed state
* @param index
*/
function change(index) {
return formChange.some((isChange) => isChange);
}
/**
* Whether form is submitted
* @param index
*/
const isSubmit = (0, react_1.useCallback)((index) => {
return isFormSubmit[index || utils_1.EMPTY_NUMBER];
}, [isFormSubmit]);
/**
* Defines submit state of form
* @param submit
* @param index
*/
function setSubmit(submit, index) {
setIsFormSubmit((isSubmit) => {
isSubmit[index || utils_1.EMPTY_NUMBER] = submit;
return isSubmit;
});
}
/**
* Whether the form array item is processed
* @param index
*/
const isFormProcess = (0, react_1.useCallback)((index) => {
return index !== void 0
? isFormProcessValue[index]
: isFormProcessValue[utils_1.EMPTY_NUMBER];
}, [isFormProcessValue]);
/**
* Defines Form Processed state
* @param process
* @param index
*/
function setFormProcess(process, index) {
setIsFormProcessValue((isProcess) => {
isProcess[index || utils_1.EMPTY_NUMBER] = process;
return isProcess;
});
}
/**
* Submit value for every form element
* @param submit
*/
function setSubmitElements(submit) {
setIsFormSubmit(schema.map(() => submit));
}
/**
* Checks whether element is submit
* @param index
*/
function isElementSubmit(index) {
return index !== void 0 ? isFormSubmit[index] : isFormSubmit[utils_1.EMPTY_NUMBER];
}
/**
* Sets the element submit
* @param submit
* @param index
*/
function setElementSubmit(submit, index) {
if (index !== void 0) {
setIsFormSubmit(isFormSubmit.map((formItemSubmit, formIndex) => formIndex === index ? submit : formItemSubmit));
}
else {
const tmpIsSubmit = isFormSubmit;
tmpIsSubmit[utils_1.EMPTY_NUMBER] = submit;
setIsFormSubmit(tmpIsSubmit);
}
}
/**
* Composes initial schema and form value state
* @param initialSchema
* @param body
*/
function composeSchema(initialSchema, body) {
setSchema(initialSchema);
const initialValue = Array(initialSchema.length);
initialSchema.forEach((schema, index) => {
schema.form.forEach((field) => {
if (!initialValue[index]) {
initialValue[index] = {};
}
initialValue[index][field.name] = body
? body[field.name]
: field.value;
});
});
// @todo: how to compose initial value of form array?
setValue(validate(initialValue, initialSchema));
}
/**
* Define Form value, submit form action to provide on-fly validation
* setFormValue sets that the form has been submitted firstly
* setFormValue sets that the form should be processed on server
* setFormValue validates the incoming user form values
* @param formValue
* @param index
* @param isProcess
*/
function setFormValue(formValue, isProcess, index) {
setSubmit(!!isProcess, index || void 0);
setFormProcess(!!isProcess, index || void 0);
if (index !== void 0) {
const tmpValue = value || utils_1.EMPTY_ARRAY;
tmpValue[index] = formValue;
setValue(validate(tmpValue, schema, index || void 0));
}
else {
setValue(validate(formValue, schema, index || void 0));
}
if (hasError(index)) {
setSubmit(false, index);
setFormProcess(false, index);
}
}
/**
* @todo: keyof schema.form
* @param fields
* @param index
*/
function getValue(fields, index) {
const applyValue = {};
if (fields) {
fields.forEach((field) => {
if (value) {
// @note: do we need mapper there?
applyValue[field] = value[index || utils_1.EMPTY_NUMBER][field];
}
});
return applyValue;
}
return value[index || utils_1.EMPTY_NUMBER];
}
/**
* Define Form Field value
* @param fieldName
* @param fieldValue
* @param index
*/
function setFieldValue(fieldName, fieldValue, index) {
const currentFieldValue = Object.assign(Object.assign({}, value[index || utils_1.EMPTY_NUMBER]), { [fieldName]: fieldValue });
const formValue = [...value];
formValue[index || utils_1.EMPTY_NUMBER] = currentFieldValue;
setFormChange((isChange) => {
isChange[index || utils_1.EMPTY_NUMBER] = true;
return isChange;
});
setFormProcess(false, index || void 0);
setValue(isFormSubmit[index || utils_1.EMPTY_NUMBER]
? validate(formValue, schema, index || void 0)
: formValue);
}
/**
* Whether form has error
*/
const hasError = (0, react_1.useCallback)((index) => {
return (value === void 0 ||
value.length === utils_1.EMPTY_NUMBER ||
value[index || utils_1.EMPTY_NUMBER] === void 0 ||
Object.values(value[index || utils_1.EMPTY_NUMBER]).some((instance) => instance instanceof form_error_1.FormError));
}, [value]);
/**
* Returns the error message by field name
* @param fieldName
* @param index
*/
function getError(fieldName, index) {
const fieldValue = value[index || utils_1.EMPTY_NUMBER][fieldName];
return value !== void 0 &&
value.length !== 0 &&
fieldValue instanceof form_error_1.FormError
? fieldValue.getMessage()
: void 0;
}
/**
* Return Form Field value to be an error or valid
* @param fieldName
* @param index
*/
const getFieldValue = (0, react_1.useCallback)((fieldName, index) => {
const fieldValue = value &&
value[index || utils_1.EMPTY_NUMBER] &&
value[index || utils_1.EMPTY_NUMBER][fieldName];
return value !== void 0 && value.length !== utils_1.EMPTY_NUMBER
? fieldValue instanceof form_error_1.FormError
? fieldValue.getValue()
: fieldValue
: void 0;
}, [value]);
/**
* Whether field has error
* @param fieldName
* @param index
*/
function isError(fieldName, index) {
return (
// isFormSubmit &&
// isFormSubmit[index || EMPTY_NUMBER] &&
value !== void 0 &&
value !== utils_1.EMPTY_ARRAY &&
value[index || utils_1.EMPTY_NUMBER] &&
value[index || utils_1.EMPTY_NUMBER][fieldName] instanceof form_error_1.FormError);
}
/**
* Validate form values with FormError exception class
* @param formValue
* @param initialSchema
* @param validationIndex
*/
function validate(formValue, initialSchema, validationIndex) {
const validationValue = formValue
? [...formValue]
: utils_1.EMPTY_ARRAY;
if (initialSchema.length !== 0) {
initialSchema.forEach((schema, index) => {
schema.form.forEach((field) => {
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
field.validation &&
Object.keys(field.validation).forEach(
// @note: type: FormFieldValidationType
(type) => {
const validationValueItem = validationValue[validationIndex || index || utils_1.EMPTY_NUMBER][field.name];
if (!((validationValueItem === null || validationValueItem === void 0 ? void 0 : validationValueItem.value) instanceof form_error_1.FormError) &&
field.validation &&
formValue) {
const fieldFormValue = formValue[validationIndex || index || utils_1.EMPTY_NUMBER][field.name];
validationValue[validationIndex || index || utils_1.EMPTY_NUMBER][field.name] =
fieldFormValue instanceof form_error_1.FormError
? fieldFormValue
: validationOperation({
type: type,
constraint: field.validation[type],
value: formValue && fieldFormValue instanceof form_error_1.FormError
? fieldFormValue.getValue()
: formValue
? fieldFormValue
: void 0,
message: field.messages && field.messages.validation
? field.messages.validation[type]
: void 0,
});
}
});
});
});
}
else {
console.error("Form Builder > Schema is no defined.");
}
return validationValue;
}
/**
* Validate field operation
* @note: can be external of context, no dependencies now
* @param {FormValidationOperationOptions} options - Object containing validation parameters
* @param {FormFieldValidationType} options.type - Type of validation operation to be performed
* @param {any} options.constraint - Constraint value for validation check
* @param {any} options.value - Value to be validated
* @param {string} options.message - Custom message for error, if validation fails
* @return {any} - Returns either value or FormError object based on validation result
* @private
*/
function validationOperation({ type, constraint, value, message, }) {
const validationResult = {
[FormFieldValidationType.MaxLength]: (validationValue) => {
if (validationValue &&
constraint &&
validationValue.length > constraint) {
return new form_error_1.FormError({
type,
constraint,
value,
message: message !== null && message !== void 0 ? message : `This field can have a maximum of ${constraint} characters`,
});
}
return value;
},
[FormFieldValidationType.MinLength]: (validationValue) => {
if (!validationValue ||
(constraint && validationValue.length < constraint)) {
return new form_error_1.FormError({
type,
constraint,
value,
message: message !== null && message !== void 0 ? message : `This field requires at least ${constraint} characters`,
});
}
return value;
},
[FormFieldValidationType.Alphabetically]: (validationValue) => {
const regExp = new RegExp(/^[a-zA-Z]+$/, "gi");
if (!validationValue ||
(constraint === true && !regExp.test(validationValue))) {
return new form_error_1.FormError({
type,
constraint,
value,
message: message !== null && message !== void 0 ? message : "Only letters are allowed in this field",
});
}
return value;
},
[FormFieldValidationType.Numerical]: (validationValue) => {
const regExp = new RegExp(/^[a-zA-Z]+$/, "gi");
if (!validationValue ||
(constraint === true && !regExp.test(validationValue))) {
return new form_error_1.FormError({
type,
constraint,
value,
message: message !== null && message !== void 0 ? message : "Only letters are allowed in this field",
});
}
return value;
},
[FormFieldValidationType.Password]: (validationValue) => {
if (validationValue && constraint) {
const capitalLetters = validationValue.match(/[A-Z]/g);
const lowercaseLetters = validationValue.match(/[a-z]/g);
const numbers = validationValue.match(/[1-9]/g);
const validationInstance = [
capitalLetters,
lowercaseLetters,
numbers,
];
const constraintValue = Number(constraint);
if (validationInstance.some((instance) => instance === null) ||
validationInstance.some((instance) => Number.isNaN(instance)) ||
validationInstance.some((item) => !item || item.length < constraintValue)) {
return new form_error_1.FormError({
type,
constraint,
value,
message: message !== null && message !== void 0 ? message : "Your password must include at least one uppercase letter, one lowercase letter, and one number.",
});
}
return value;
}
return value;
},
[FormFieldValidationType.ShouldExist]: (validationValue) => {
if (!validationValue ||
(constraint === true &&
(validationValue === false ||
validationValue === null ||
validationValue === void 0 ||
validationValue === utils_1.EMPTY_STRING))) {
return new form_error_1.FormError({
type,
constraint,
value,
message: message !== null && message !== void 0 ? message : "Please fill out this field", // @todo: message from form builder schema
});
}
return value;
},
[FormFieldValidationType.Email]: (validationValue) => {
const regExp = new RegExp("^[a-zA-Z0-9._+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,4}$", "gi");
if (constraint === true && !regExp.test(validationValue)) {
return new form_error_1.FormError({
type,
constraint,
value,
message: message !== null && message !== void 0 ? message : "Please enter a valid email address", // @todo: message from form builder schema
});
}
return value;
},
}[type](value);
return validationResult;
}
return (React.createElement(exports.FormsContext.Provider, { value: {
value,
setValue,
getFieldValue,
setFieldValue,
isError,
getError,
setFormValue,
getValue,
hasError,
setSchema,
composeSchema,
isSubmit,
setSubmit,
isFormProcess,
setFormProcess,
change,
setChange,
i18nSchema,
} }, children));
};
exports.FormProvider = FormProvider;
//# sourceMappingURL=forms.context.js.map