UNPKG

@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
"use strict"; /* * 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