├── .gitignore ├── .npmignore ├── LICENSE ├── dist ├── cors │ ├── ErrorHandler.d.ts │ ├── ErrorManager.d.ts │ ├── EventManger.d.ts │ ├── FieldManager.d.ts │ ├── FormHandler.d.ts │ ├── HooksHandler.d.ts │ ├── LanguageManager.d.ts │ ├── PerformanceOptimizer.d.ts │ ├── Validator.d.ts │ └── ValidatorManager.d.ts ├── index.d.ts ├── interfaces │ └── index.d.ts ├── shadow-form-handler.js └── validators │ └── ValidationRules.d.ts ├── package-lock.json ├── package.json ├── readme.md ├── src ├── cors │ ├── ErrorManager.ts │ ├── EventManger.ts │ ├── FieldManager.ts │ ├── FormHandler.ts │ ├── HooksHandler.ts │ ├── LanguageManager.ts │ └── ValidatorManager.ts ├── index.ts ├── interfaces │ └── index.ts └── validators │ └── ValidationRules.ts ├── tsconfig.json └── webpack.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | my-app 3 | .vscode -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | src/ 2 | tsconfig.json 3 | webpack.config.js 4 | .gitignore -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Mostafa Mohamed Abdalla 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | For more tutorials and coding tips, visit my YouTube channel: [Shadow Coding](https://www.youtube.com/@ShadowCoding1) 24 | Connect with me on LinkedIn: [Mostafa Mohamed](https://www.linkedin.com/in/mostafashadow1) 25 | -------------------------------------------------------------------------------- /dist/cors/ErrorHandler.d.ts: -------------------------------------------------------------------------------- 1 | import { Field, ErrorStyle } from '../interfaces/index'; 2 | /** 3 | * ErrorHandler class: Manages error display and styling. 4 | */ 5 | export declare class ErrorHandler { 6 | private errorStyles; 7 | /** 8 | * Set error styles to be applied to error elements. 9 | * @param {ErrorStyle} style - An error style object to be applied. 10 | */ 11 | setErrorStyles(style: ErrorStyle): void; 12 | /** 13 | * Apply the error styles to a given element. 14 | * @param element - The element to apply styles to. 15 | */ 16 | applyStyles(element: HTMLElement): void; 17 | /** 18 | * Display errors for all form fields. 19 | * @param {Field[]} fields - An array of all form fields. 20 | */ 21 | displayErrors(fields: Field[]): void; 22 | /** 23 | * Clear all error messages. 24 | */ 25 | clearErrors(): void; 26 | } 27 | -------------------------------------------------------------------------------- /dist/cors/ErrorManager.d.ts: -------------------------------------------------------------------------------- 1 | import { Field, ErrorStyle } from '../interfaces/index'; 2 | import { FieldManager } from './FieldManager'; 3 | /** 4 | * ErrorManager class: Manages error display and styling. 5 | */ 6 | export declare class ErrorManager { 7 | private fieldManager; 8 | constructor(fieldManager: FieldManager); 9 | private errorStyles; 10 | /** 11 | * Set error styles to be applied to error elements. 12 | * @param {ErrorStyle} style - An error style object to be applied. 13 | */ 14 | setErrorStyles(style: ErrorStyle): void; 15 | /** 16 | * Apply the error styles to a given element. 17 | * @param element - The element to apply styles to. 18 | */ 19 | applyStyles(element: HTMLElement): void; 20 | /** 21 | * Display errors for all form fields. 22 | * @param {Field[]} fields - An array of all form fields. 23 | */ 24 | displayErrors(fields: Field[]): void; 25 | /** 26 | * Clear all error messages. 27 | */ 28 | clearErrors(): void; 29 | /** 30 | * Check if any field has errors. 31 | * @returns {boolean} True if any field has an error, false otherwise. 32 | */ 33 | hasErrors(): boolean; 34 | /** 35 | * get all errors and return it 36 | * @returns {Array} get all erros 37 | */ 38 | getErrors(): {}; 39 | } 40 | -------------------------------------------------------------------------------- /dist/cors/EventManger.d.ts: -------------------------------------------------------------------------------- 1 | import { FieldManager } from './FieldManager'; 2 | import { ValidatorManager } from './ValidatorManager'; 3 | import { HooksHandler } from './HooksHandler'; 4 | /** 5 | * EventManager class: Manages form-related events and hooks. 6 | */ 7 | export declare class EventManager { 8 | private fieldManager; 9 | private validator; 10 | private hooksHandler; 11 | constructor(fieldManager: FieldManager, validator: ValidatorManager, hooksHandler: HooksHandler); 12 | /** 13 | * Add runtime validation to a form field. 14 | * @param {string} id - The id of the form field. 15 | */ 16 | addRuntimeValidation(id: string): void; 17 | /** 18 | * Handle input change event. 19 | * @param {Event} event - The input change event. 20 | */ 21 | private handleInputChange; 22 | /** 23 | * Add a custom event listener to a form field. 24 | * @param {string} id - The id of the form field. 25 | * @param {string} event - The event type to listen for. 26 | * @param {EventListener} listener - The event listener function. 27 | */ 28 | addCustomEventListener(id: string, event: string, listener: EventListener): void; 29 | } 30 | -------------------------------------------------------------------------------- /dist/cors/FieldManager.d.ts: -------------------------------------------------------------------------------- 1 | import { addFieldParams, Field, RegisterParams } from '../interfaces/index'; 2 | /** 3 | * FieldManager class: Manages form fields, their values, and related DOM operations. 4 | */ 5 | export declare class FieldManager { 6 | private fields; 7 | /** 8 | * Register a new form field. 9 | * @param {RegisterParams} params - The parameters for registering a field. 10 | */ 11 | register(params: RegisterParams): void; 12 | /** 13 | * Dynamically remove a form field. 14 | * @param {string} fieldId - The id of the form field. 15 | * @param {string} disabledId -The ID of a button element that should be disabled when the field is added. 16 | */ 17 | removeField(params: { 18 | fieldId: string; 19 | disabledId: string; 20 | }): void; 21 | /** 22 | * Add a new form field to the DOM and register it. 23 | * @param {Object} params - Parameters for adding a new field. 24 | * @param {string} params.containerId - The ID of the container element where the field should be added. 25 | * @param {string} params.fieldId - The ID of the new form field. 26 | * @param {string} params.labelText - The text to display as the label for the form field. 27 | * @param {RegisterParams} [params.register] - Optional parameters for registering the field. 28 | * @param {string|number} [params.position] - The position where the new field should be inserted. 29 | * @param {string} [params.disabledId] - The ID of a button element that should be disabled when the field is added. 30 | */ 31 | addField(params: addFieldParams): void; 32 | /** 33 | * Get a field by its ID. 34 | * @param {string} id - The id of the form field. 35 | * @returns {Field|undefined} The field object if found, undefined otherwise. 36 | */ 37 | getField(id: string): Field | undefined; 38 | /** 39 | * Get all form fields. 40 | * @returns {Field[]} An array of all form fields. 41 | */ 42 | getAllFields(): Field[]; 43 | /** 44 | * Set the value of a form field. 45 | * @param {string} id - The id of the form field. 46 | * @param {string} value - The new value to set. 47 | */ 48 | setValue(id: string, value: string): void; 49 | /** 50 | * Get the value of a form field. 51 | * @param {string} id - The id of the form field. 52 | * @returns {string|undefined} The value of the field if found, undefined otherwise. 53 | */ 54 | getValue(id: string): string | undefined; 55 | /** 56 | * Get all form field values. 57 | * @returns {Object} An object with all form field ids as keys and their current values as values. 58 | */ 59 | getValues(): { 60 | [key: string]: string; 61 | }; 62 | /** 63 | * Reset all form fields to their initial state. 64 | */ 65 | resetFields(): void; 66 | } 67 | -------------------------------------------------------------------------------- /dist/cors/FormHandler.d.ts: -------------------------------------------------------------------------------- 1 | import { Mode, Hooks, RegisterParams, ErrorStyle, addFieldParams } from '../interfaces/index'; 2 | import * as validation from "../validators/ValidationRules"; 3 | /** 4 | * FormHandler class: Manages form validation, registration, and submission. 5 | */ 6 | export declare class FormHandler { 7 | private fieldManager; 8 | private hooksHandler; 9 | private validatorManager; 10 | private errorManager; 11 | private eventManager; 12 | private mode; 13 | /** 14 | * Built-in validation that can be used directly. 15 | */ 16 | validation: typeof validation; 17 | /** 18 | * Built-in lang that can be used directly. 19 | */ 20 | lang: import("./LanguageManager").LanguageManager; 21 | constructor(); 22 | /** 23 | * Register a form field with initial value and validators. 24 | * @param {RegisterParams} params - The parameters for registering a field. 25 | */ 26 | register(params: RegisterParams): void; 27 | /** 28 | * Set up validation for a field and its dependencies. 29 | * @param {string} fieldId - The ID of the field to set up validation for. 30 | */ 31 | private setupFieldValidation; 32 | /** 33 | * Set the mode for the form handler. 34 | * @param {Mode} newMode - The new mode to set. 35 | */ 36 | setMode(newMode: Mode): void; 37 | /** 38 | * Handle form submission. 39 | * @param {string} formId - The ID of the form element. 40 | * @param {Function} [onSubmit] - Optional custom submission handler. 41 | */ 42 | submitHandler(formId: string, onSubmit?: (values: { 43 | [key: string]: string; 44 | }) => void): void; 45 | /** 46 | * Get the current value of a form field. 47 | * @param {string} id - The id of the form field. 48 | * @returns {string|undefined} The value of the form field. 49 | */ 50 | getValue(id: string): string | undefined; 51 | /** 52 | * Get all form field values. 53 | * @returns {Object} An object with all form field ids as keys and their current values as values. 54 | */ 55 | getValues(): { 56 | [key: string]: string; 57 | }; 58 | /** 59 | * Reset the form fields and errors. 60 | */ 61 | resetForm(): void; 62 | /** 63 | * Dynamically remove a form field. 64 | * @param {string} fieldId - The id of the form field. 65 | * @param {string} disabledId -The ID of a button element that should be disabled when the field is added. 66 | */ 67 | removeField(params: { 68 | fieldId: string; 69 | disabledId: string; 70 | }): void; 71 | /** 72 | * Dynamically add a form field to a specific position in the container. 73 | * @param {Object} params - Parameters for adding a new field. 74 | * @param {string} params.containerId - The ID of the container element where the field should be added. 75 | * @param {string} params.fieldId - The ID of the new form field. 76 | * @param {string} params.labelText - The text to display as the label for the form field. 77 | * @param {RegisterParams} [params.register] - Optional parameters for registering the field. 78 | * @param {string|number} [params.position] - The position where the new field should be inserted. 79 | * @param {string} [params.disabled] - The ID of a button element that should be disabled when the field is added. 80 | */ 81 | addField(params: addFieldParams): void; 82 | /** 83 | * Set error styles to be applied to error elements. 84 | * @param {ErrorStyle} style - An error style object to be applied. 85 | */ 86 | setErrorStyles(style: ErrorStyle): void; 87 | /** 88 | * Add hooks for the form handler. 89 | * @param {Hooks} hooks - Hooks to be set. 90 | */ 91 | addHooks(hooks: Hooks): void; 92 | /** 93 | * Attach a custom event listener to a form field. 94 | * @param {string} id - The id of the form field. 95 | * @param {string} event - The event type (e.g., 'input', 'change'). 96 | * @param {EventListener} listener - The event listener function. 97 | */ 98 | addCustomEventListener(id: string, event: string, listener: EventListener): void; 99 | /** 100 | * Attaches focus and blur event listeners to an input field. 101 | * @param {id} id - The parameters required to register the field. 102 | */ 103 | attachFocusBlurListeners(id: string): void; 104 | } 105 | -------------------------------------------------------------------------------- /dist/cors/HooksHandler.d.ts: -------------------------------------------------------------------------------- 1 | import { Hooks } from '../interfaces/index'; 2 | export declare class HooksHandler { 3 | private hooks; 4 | /** 5 | * Add hooks for form events. 6 | * @param {Hooks} hooks - Hooks to be added. 7 | */ 8 | addHooks(hooks: Hooks): void; 9 | /** 10 | * Get all hooks. 11 | * @returns {Hooks} - The hooks object. 12 | */ 13 | getHooks(): Hooks; 14 | /** 15 | * Get a specific hook by name. 16 | * @param {keyof Hooks} hookName - The name of the hook to retrieve. 17 | * @returns {Function | undefined} - The hook function or undefined if not found. 18 | */ 19 | getHook(hookName: keyof Hooks): ((values: { 20 | [key: string]: string; 21 | }) => void) | ((errors: { 22 | [key: string]: string; 23 | }) => void) | ((fieldId: string, error: string) => void) | (() => void) | ((fieldId: string, params: import("../interfaces/index").RegisterParams) => void) | ((fieldId: string, params: import("../interfaces/index").RegisterParams) => void) | ((field: import("../interfaces/index").Field) => void) | ((field: import("../interfaces/index").Field) => void) | ((fieldId: string, value: string) => void) | ((fields: import("../interfaces/index").Field[]) => void) | ((fields: import("../interfaces/index").Field[]) => void) | ((fieldId: string) => void) | ((fieldId: string) => void) | undefined; 24 | /** 25 | * Trigger a specific hook. 26 | * @param {keyof Hooks} hookName - The name of the hook to trigger. 27 | * @param {...any} args - Arguments to pass to the hook. 28 | */ 29 | triggerHook(hookName: keyof Hooks, ...args: any): void; 30 | } 31 | -------------------------------------------------------------------------------- /dist/cors/LanguageManager.d.ts: -------------------------------------------------------------------------------- 1 | export declare class LanguageManager { 2 | private translations; 3 | private currentLanguage; 4 | /** 5 | * Set the current language. 6 | * @param {string} langCode - The language code to set. 7 | */ 8 | setCurrentLanguage(langCode: string): void; 9 | /** 10 | * get the current language. 11 | */ 12 | getCurrentLanguage(): string; 13 | /** 14 | * Add translations for a specific language. 15 | * @param {string} lang - The language code. 16 | * @param {Translations} translations - The translations to add. 17 | */ 18 | addTranslations(lang: string, translations: { 19 | [key: string]: string; 20 | }): void; 21 | /** 22 | * Get the translation for a key in the current language. 23 | * If the translation key is not found, the default message will be returned. 24 | * If a parameter is not found, the placeholder will be used as the default value and a warning will be logged. 25 | * @param {string} key - The key to translate. 26 | * @param {Object} [params] - Parameters to interpolate in the translated string. 27 | * @param {string} [defaultMessage] - The default message to use if the translation is not found. 28 | * @returns {string} The translated string, or the default message if the translation is not found. 29 | */ 30 | getTranslation(key: string, params?: { 31 | [key: string]: string | number; 32 | }, defaultMessage?: string): string; 33 | } 34 | export declare const lang: LanguageManager; 35 | -------------------------------------------------------------------------------- /dist/cors/PerformanceOptimizer.d.ts: -------------------------------------------------------------------------------- 1 | import { Field } from "../interfaces"; 2 | export declare class PerformanceOptimizer { 3 | private debounceTimers; 4 | /** 5 | * Debounces a function call. 6 | * @param {string} key - A unique identifier for the debounced function. 7 | * @param {Function} fn - The function to debounce. 8 | * @param {number} delay - The debounce delay in milliseconds. 9 | */ 10 | debounce(key: string, fn: () => void, delay: number): void; 11 | /** 12 | * Batches DOM updates for multiple fields. 13 | * @param {Field[]} fields - The fields to update. 14 | * @param {(field: Field) => void} updateFn - The function to call for each field update. 15 | */ 16 | batchDomUpdates(fields: Field[], updateFn: (field: Field) => void): void; 17 | /** 18 | * Efficiently validates multiple fields. 19 | * @param {Field[]} fields - The fields to validate. 20 | * @param {(field: Field) => Promise} validateFn - The validation function. 21 | * @returns {Promise<{ [key: string]: string | null }>} A promise that resolves to an object of field errors. 22 | */ 23 | validateFields(fields: Field[], validateFn: (field: Field) => Promise): Promise<{ 24 | [key: string]: string | null; 25 | }>; 26 | } 27 | -------------------------------------------------------------------------------- /dist/cors/Validator.d.ts: -------------------------------------------------------------------------------- 1 | import { FieldManager } from './FieldManager'; 2 | import { ErrorHandler } from './ErrorHandler'; 3 | import { HooksHandler } from './HooksHandler'; 4 | /** 5 | * Validator class: Handles form field validation. 6 | */ 7 | export declare class Validator { 8 | private fieldManager; 9 | private errorHandler; 10 | private hooksHandler; 11 | constructor(fieldManager: FieldManager, errorHandler: ErrorHandler, hooksHandler: HooksHandler); 12 | /** 13 | * Validate all form fields. 14 | * @returns {Promise} 15 | */ 16 | validateAll(): Promise; 17 | /** 18 | * Validate a single form field. 19 | * @param {string} id - The id of the form field to validate. 20 | * @returns {Promise} 21 | */ 22 | validateField(id: string): Promise; 23 | /** 24 | * Validate a field against its schema validators. 25 | * @param {Field} field - The form field to validate. 26 | * @returns {string|null} The error message if validation fails, otherwise null. 27 | */ 28 | private validateFieldSchema; 29 | /** 30 | * Perform custom validations on a given form field. 31 | * @param {Field} field - The form field to validate. 32 | * @returns {Promise} The error message if validation fails, otherwise null. 33 | */ 34 | private validateCustomValidator; 35 | /** 36 | * Sets up validation for a field that depends on another field's value. 37 | * @param field - The field that requires validation. 38 | * @param dependentFieldId - The ID of the field that this field depends on. 39 | * @param validate - A function that validates the field's value. It returns a boolean or a Promise that resolves to a boolean. 40 | * @param message - The error message to display if validation fails. 41 | */ 42 | private setupDependentValidation; 43 | /** 44 | * Updates the error message for a field and displays all errors. 45 | * @param field - The field to update. 46 | * @param error - The error message to set, or null if there is no error. 47 | */ 48 | private updateFieldError; 49 | } 50 | -------------------------------------------------------------------------------- /dist/cors/ValidatorManager.d.ts: -------------------------------------------------------------------------------- 1 | import { FieldManager } from './FieldManager'; 2 | import { ErrorManager } from './ErrorManager'; 3 | import { HooksHandler } from './HooksHandler'; 4 | /** 5 | * ValidatorManager class: Handles form field validation. 6 | */ 7 | export declare class ValidatorManager { 8 | private fieldManager; 9 | private errorManager; 10 | private hooksHandler; 11 | constructor(fieldManager: FieldManager, errorManager: ErrorManager, hooksHandler: HooksHandler); 12 | /** 13 | * Validate all form fields. 14 | * @returns {Promise} 15 | */ 16 | validateAll(): Promise; 17 | /** 18 | * Validate a single form field. 19 | * @param {string} id - The id of the form field to validate. 20 | * @returns {Promise} 21 | */ 22 | validateField(id: string): Promise; 23 | /** 24 | * Validate a field against its schema validators. 25 | * @param {Field} field - The form field to validate. 26 | * @param {{ [key: string]: string }} formValues - The current values of all form fields. 27 | * @returns {string|null} The error message if validation fails, otherwise null. 28 | */ 29 | private validateFieldSchema; 30 | /** 31 | * Perform custom validations on a given form field. 32 | * @param {Field} field - The form field to validate. 33 | * @param {{ [key: string]: string }} formValues - The current values of all form fields. 34 | * @returns {Promise} The error message if validation fails, otherwise null. 35 | */ 36 | private validateCustomValidator; 37 | /** 38 | * Sets up validation for a field that depends on another field's value. 39 | * @param {string} fieldId - The ID of the field that depends on others. 40 | * @param {string[]} dependencies - The IDs of the fields this field depends on. 41 | */ 42 | setupDependencyListeners(fieldId: string, dependencies: string[]): void; 43 | /** 44 | * Updates the error message for a field and displays all errors. 45 | * @param {Field} field - The field to update. 46 | * @param {string | null} error - The error message to set, or null if there is no error. 47 | */ 48 | private updateFieldError; 49 | } 50 | -------------------------------------------------------------------------------- /dist/index.d.ts: -------------------------------------------------------------------------------- 1 | import { FormHandler } from './cors/FormHandler'; 2 | import { required, minLength, maxLength, pattern, email } from "./validators/ValidationRules"; 3 | export { FormHandler, required, minLength, maxLength, pattern, email }; 4 | -------------------------------------------------------------------------------- /dist/interfaces/index.d.ts: -------------------------------------------------------------------------------- 1 | export type SchemaValidator = (value: string, formValues: { 2 | [key: string]: string; 3 | }) => string | null; 4 | export declare const enum Mode { 5 | Default = "default", 6 | Runtime = "runtime" 7 | } 8 | export interface CustomValidator { 9 | validate: (value: string, formValues: { 10 | [key: string]: string; 11 | }) => boolean; 12 | message: string; 13 | } 14 | export interface Field { 15 | id: string; 16 | value: string; 17 | error: string | null; 18 | customValidation?: Array; 19 | schemaValidation?: Array; 20 | dependencies?: string[]; 21 | } 22 | export interface Hooks { 23 | onFormSubmitSuccess?: (values: { 24 | [key: string]: string; 25 | }) => void; 26 | onFormSubmitFail?: (errors: { 27 | [key: string]: string; 28 | }) => void; 29 | onFieldValidationError?: (fieldId: string, error: string) => void; 30 | onFormReset?: () => void; 31 | beforeFieldRegister?: (fieldId: string, params: RegisterParams) => void; 32 | afterFieldRegister?: (fieldId: string, params: RegisterParams) => void; 33 | onFocus?: (field: Field) => void; 34 | onBlur?: (field: Field) => void; 35 | onValueChange?: (fieldId: string, value: string) => void; 36 | onValidationStart?: (fields: Field[]) => void; 37 | onValidationEnd?: (fields: Field[]) => void; 38 | onFieldAdd?: (fieldId: string) => void; 39 | onFieldRemove?: (fieldId: string) => void; 40 | } 41 | export interface RegisterParams { 42 | id: string; 43 | initialValue?: string; 44 | schemaValidation?: Array; 45 | customValidation?: Array; 46 | dependencies?: string[]; 47 | } 48 | export interface ErrorStyle { 49 | property: string; 50 | value: string; 51 | } 52 | export interface addFieldParams { 53 | containerId: string; 54 | fieldId: string; 55 | labelText: string; 56 | register?: RegisterParams; 57 | position?: "top" | "bottom" | number; 58 | disabledId?: string; 59 | } 60 | -------------------------------------------------------------------------------- /dist/shadow-form-handler.js: -------------------------------------------------------------------------------- 1 | (()=>{"use strict";var e,t,n={811:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.ErrorManager=void 0;var n=function(){function e(e){this.fieldManager=e,this.errorStyles={property:" ",value:""}}return e.prototype.setErrorStyles=function(e){this.errorStyles=e},e.prototype.applyStyles=function(e){for(var t=0,n=Object.entries(this.errorStyles);t0&&i[i.length-1])||6!==l[0]&&2!==l[0])){a=0;continue}if(3===l[0]&&(!i||l[1]>i[0]&&l[1]{Object.defineProperty(t,"__esModule",{value:!0}),t.FieldManager=void 0;var n=function(){function e(){this.fields=[]}return e.prototype.register=function(e){var t=this,n=e.id,r=e.schemaValidation,i=e.customValidation,o=e.initialValue,a=e.dependencies;this.fields.push({id:n,value:o||"",error:null,schemaValidation:r,customValidation:i,dependencies:a});var l=document.getElementById(n);if(!l)throw new Error("Input element with id ".concat(n," not found"));l.addEventListener("input",(function(n){var r=n.target;t.setValue(e.id,r.value)})),o&&(l.value=o)},e.prototype.removeField=function(e){var t=document.getElementById(e.fieldId);if(t){if(e.disabledId){var n=document.getElementById(e.disabledId);n&&(n.disabled=!0)}this.fields=this.fields.filter((function(t){return t.id!==e.fieldId}));var r=document.getElementById("".concat(e.fieldId,"-error")),i=document.querySelector('label[for="'.concat(e.fieldId,'"]'));t.remove(),null==r||r.remove(),null==i||i.remove()}else console.error("Element with id ".concat(e.fieldId," not found."))},e.prototype.addField=function(e){var t,n,r;if(document.getElementById(e.fieldId))console.warn("Field with id ".concat(e.fieldId," already exists. Skipping creation."));else{if(e.disabledId){var i=document.getElementById(e.disabledId);i&&(i.disabled=!0)}var o=document.getElementById(e.containerId);if(!o)throw new Error("Container element with id ".concat(e.containerId," not found"));var a=document.createElement("div"),l=document.createElement("label"),s=document.createElement("input"),u=document.createElement("span");if(l.setAttribute("for",e.fieldId),l.textContent=e.labelText,s.setAttribute("id",e.fieldId),s.setAttribute("name",e.fieldId),s.setAttribute("type","text"),u.setAttribute("id","".concat(e.fieldId,"-error")),u.classList.add("error-message"),a.appendChild(l),a.appendChild(s),a.appendChild(u),e.position)if("top"===e.position)o.insertBefore(a,o.firstChild);else if("bottom"===e.position)o.appendChild(a);else{if(!("number"==typeof e.position&&e.position>=0))throw new Error("Invalid position value: ".concat(e.position));var d=o.children[e.position];d&&o.insertBefore(a,d)}else o.appendChild(a);this.register({id:e.fieldId,initialValue:(null===(t=e.register)||void 0===t?void 0:t.initialValue)||"",schemaValidation:(null===(n=e.register)||void 0===n?void 0:n.schemaValidation)||[],customValidation:(null===(r=e.register)||void 0===r?void 0:r.customValidation)||[]})}},e.prototype.getField=function(e){return this.fields.find((function(t){return t.id===e}))},e.prototype.getAllFields=function(){return this.fields},e.prototype.setValue=function(e,t){var n=this.getField(e);n&&(n.value=t)},e.prototype.getValue=function(e){var t=this.getField(e);return null==t?void 0:t.value},e.prototype.getValues=function(){var e={};return this.fields.forEach((function(t){e[t.id]=t.value})),e},e.prototype.resetFields=function(){this.fields.forEach((function(e){e.value="",e.error=null;var t=document.getElementById(e.id);t&&(t.value="")}))},e}();t.FieldManager=n},830:function(e,t,n){var r=this&&this.__createBinding||(Object.create?function(e,t,n,r){void 0===r&&(r=n);var i=Object.getOwnPropertyDescriptor(t,n);i&&!("get"in i?!t.__esModule:i.writable||i.configurable)||(i={enumerable:!0,get:function(){return t[n]}}),Object.defineProperty(e,r,i)}:function(e,t,n,r){void 0===r&&(r=n),e[r]=t[n]}),i=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),o=this&&this.__importStar||function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var n in e)"default"!==n&&Object.prototype.hasOwnProperty.call(e,n)&&r(t,e,n);return i(t,e),t},a=this&&this.__awaiter||function(e,t,n,r){return new(n||(n=Promise))((function(i,o){function a(e){try{s(r.next(e))}catch(e){o(e)}}function l(e){try{s(r.throw(e))}catch(e){o(e)}}function s(e){var t;e.done?i(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(a,l)}s((r=r.apply(e,t||[])).next())}))},l=this&&this.__generator||function(e,t){var n,r,i,o,a={label:0,sent:function(){if(1&i[0])throw i[1];return i[1]},trys:[],ops:[]};return o={next:l(0),throw:l(1),return:l(2)},"function"==typeof Symbol&&(o[Symbol.iterator]=function(){return this}),o;function l(l){return function(s){return function(l){if(n)throw new TypeError("Generator is already executing.");for(;o&&(o=0,l[0]&&(a=0)),a;)try{if(n=1,r&&(i=2&l[0]?r.return:l[0]?r.throw||((i=r.return)&&i.call(r),0):r.next)&&!(i=i.call(r,l[1])).done)return i;switch(r=0,i&&(l=[2&l[0],i.value]),l[0]){case 0:case 1:i=l;break;case 4:return a.label++,{value:l[1],done:!1};case 5:a.label++,r=l[1],l=[0];continue;case 7:l=a.ops.pop(),a.trys.pop();continue;default:if(!((i=(i=a.trys).length>0&&i[i.length-1])||6!==l[0]&&2!==l[0])){a=0;continue}if(3===l[0]&&(!i||l[1]>i[0]&&l[1]{Object.defineProperty(t,"__esModule",{value:!0}),t.HooksHandler=void 0;var n=function(){function e(){this.hooks={}}return e.prototype.addHooks=function(e){this.hooks=e},e.prototype.getHooks=function(){return this.hooks},e.prototype.getHook=function(e){return this.hooks[e]},e.prototype.triggerHook=function(e){for(var t=[],n=1;n0&&i[i.length-1])||6!==l[0]&&2!==l[0])){a=0;continue}if(3===l[0]&&(!i||l[1]>i[0]&&l[1]{Object.defineProperty(t,"__esModule",{value:!0}),t.matches=t.range=t.email=t.pattern=t.maxLength=t.minLength=t.required=void 0;var r=n(253);t.required=function(e){return function(t){return t.trim()?null:e||r.lang.getTranslation("required",{},"This field is required.")}},t.minLength=function(e,t){return function(n){return n.length>=e?null:t||r.lang.getTranslation("minLength",{min:e},"This field must be at least ".concat(e," characters long."))}},t.maxLength=function(e,t){return function(n){return n.length<=e?null:t||r.lang.getTranslation("maxLength",{max:e},"This field must be no more than ".concat(e," characters long."))}},t.pattern=function(e,t){return function(n){return e.test(n)?null:t||r.lang.getTranslation("pattern",{},"This field does not match the required pattern.")}},t.email=function(e){return(0,t.pattern)(/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/,e||r.lang.getTranslation("email",{},"This field must be a valid email address."))},t.range=function(e,t,n){return function(i){var o=parseFloat(i);return!isNaN(o)&&o>=e&&o<=t?null:n||r.lang.getTranslation("range",{min:e,max:t},"The value must be between ".concat(e," and ").concat(t,"."))}},t.matches=function(e,t){return function(n,i){return i&&n===i[e]?null:t||r.lang.getTranslation("matches",{fieldToMatch:e},"This field must match the ".concat(e," field."))}}}},r={};function i(e){var t=r[e];if(void 0!==t)return t.exports;var o=r[e]={exports:{}};return n[e].call(o.exports,o,o.exports,i),o.exports}e=i(830),t=i(100),"undefined"!=typeof window&&(window.shadowFormHandler={FormHandler:e.FormHandler,required:t.required,minLength:t.minLength,maxLength:t.maxLength,pattern:t.pattern,email:t.email})})(); -------------------------------------------------------------------------------- /dist/validators/ValidationRules.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Validation rule to check if the value is required. 3 | * @param {string} [message] - The error message to display if validation fails. Defaults to the translation for 'required'. 4 | * @returns A function that validates if the value is non-empty. 5 | */ 6 | export declare const required: (message?: string) => (value: string) => string | null; 7 | /** 8 | * Validation rule to check if the value meets the minimum length requirement. 9 | * @param {number} min - The minimum length required. 10 | * @param {string} [message] - The error message to display if validation fails. Defaults to the translation for 'minLength'. 11 | * @returns A function that validates if the value has the minimum length. 12 | */ 13 | export declare const minLength: (min: number, message?: string) => (value: string) => string | null; 14 | /** 15 | * Validation rule to check if the value meets the maximum length requirement. 16 | * @param {number} max - The maximum length allowed. 17 | * @param {string} [message] - The error message to display if validation fails. Defaults to the translation for 'maxLength'. 18 | * @returns A function that validates if the value has the maximum length. 19 | */ 20 | export declare const maxLength: (max: number, message?: string) => (value: string) => string | null; 21 | /** 22 | * Validation rule to check if the value matches a regular expression pattern. 23 | * @param {RegExp} pattern - The regular expression pattern to match. 24 | * @param {string} [message] - The error message to display if validation fails. Defaults to the translation for 'pattern'. 25 | * @returns A function that validates if the value matches the pattern. 26 | */ 27 | export declare const pattern: (regex: RegExp, message?: string) => (value: string) => string | null; 28 | /** 29 | * Validation rule to check if the value is a valid email format. 30 | * @param {string} [message] - The error message to display if validation fails. Defaults to the translation for 'email'. 31 | * @returns A function that validates if the value is a valid email. 32 | */ 33 | export declare const email: (message?: string) => (value: string) => string | null; 34 | /** 35 | * Checks if the value is a number within a specified range. 36 | * @param {number} min - Minimum value (inclusive). 37 | * @param {number} max - Maximum value (inclusive). 38 | * @param {string} [message] - The error message to display if validation fails. Defaults to the translation for 'range'. 39 | */ 40 | export declare const range: (min: number, max: number, message?: string) => (value: string) => string | null; 41 | /** 42 | * Checks if the value matches another field's value. 43 | * @param {string} fieldToMatch - ID of the field to match against. 44 | * @param {string} [message] - The error message to display if validation fails. Defaults to the translation for 'matches'. 45 | */ 46 | export declare const matches: (fieldToMatch: string, message?: string) => (value: string, formValues?: { 47 | [key: string]: string; 48 | } | undefined) => string | null; 49 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "shadow-form-handler", 3 | "version": "2.0.2", 4 | "main": "dist/shadow-form-handler.js", 5 | "types": "dist/index.d.ts", 6 | "description": "v2.0.1 is a powerful and flexible form-handling and validation library for JavaScript and TypeScript. It simplifies the process of creating and managing complex forms by providing a rich set of features, an intuitive API, and an extensible architecture. Whether you’re building simple forms or complex multi-step workflows, shadow-form-handler v2.0.0 has you covered with robust validation, dynamic field management, and seamless integration capabilities.", 7 | "homepage": "https://github.com/Mostafashadow1/shadow-form-handler", 8 | "repository": { 9 | "type": "git", 10 | "url": "https://github.com/Mostafashadow1/shadow-form-handler.git" 11 | }, 12 | "scripts": { 13 | "build:dev": "webpack --mode development --config webpack.config.js", 14 | "build:prod": "webpack --mode production --config webpack.config.js", 15 | "watch": "webpack --watch --mode development --config webpack.config.js" 16 | }, 17 | "files": [ 18 | "dist", 19 | "README.md" 20 | ], 21 | "author": "Mostafa Mohamed", 22 | "license": "MIT", 23 | "devDependencies": { 24 | "lite-server": "^2.6.1", 25 | "ts-loader": "^9.2.3", 26 | "typescript": "^4.3.5", 27 | "webpack": "^5.46.0", 28 | "webpack-cli": "^4.7.2" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # shadow-form-handler 2 | 3 | ## Overview 4 | 5 | **shadow-form-handler** is a powerful and flexible form-handling and validation library for JavaScript and TypeScript. It simplifies the process of creating and managing complex forms by providing a rich set of features, an intuitive API, and an extensible architecture. Whether you’re building simple forms or complex multi-step workflows, shadow-form-handler has you covered with robust validation, dynamic field management, and seamless integration capabilities. 6 | 7 | 8 | ## 🚀 Key Features 9 | 10 | 1. **Dynamic Field Management**: 11 | - Easily add, remove, and modify form fields at runtime. 12 | - Specify the position where new fields should be inserted (top, bottom, or a specific index). 13 | - Disable buttons when adding or removing fields for a better user experience. 14 | 15 | 2. **Flexible Validation**: 16 | - Leverage both schema-based and custom validation rules. 17 | - Utilize built-in validation rules like `required`, `minLength`, `maxLength`, `pattern`, `email`, `range`, and `matches`. 18 | - Create custom validation rules with asynchronous support. 19 | - Set up validations that depend on the values of other fields. 20 | 21 | 3. **Error Handling**: 22 | - Display error messages for invalid fields with customizable styling. 23 | - Provide clear and specific error messages to enhance the user experience. 24 | - Automatically display errors when validation fails. 25 | 26 | 4. **Event Management**: 27 | - Attach custom event listeners to form fields (e.g., `input`, `change`, `blur`). 28 | - Integrate with form events like `onFormSubmitSuccess`, `onFormSubmitFail`, and `onFieldValidationError`. 29 | 30 | 5. **Hooks System**: 31 | - Extend the functionality of the form handler by adding custom hooks. 32 | - Trigger hooks at various stages of the form lifecycle, such as field registration, validation, and submission. 33 | - Use hooks to implement custom logic, logging, or side effects. 34 | 35 | 6. **Validation Modes**: 36 | - Choose between `default` (on form submit) and `runtime` (real-time) validation modes. 37 | - Automatically add runtime validation to new fields when they are registered. 38 | 39 | 7. **Asynchronous Validation**: 40 | - Perform asynchronous validations with ease, such as checking for uniqueness on the server. 41 | - Implement timeout handling to ensure a smooth user experience. 42 | 43 | 8. **Dependent Field Validation**: 44 | - Set up validations that depend on the values of other form fields. 45 | - Automatically re-validate dependent fields when their dependencies change. 46 | 47 | 9. **Internationalization and Localization**: 48 | - Provide translations for validation error messages. 49 | - Support multiple languages to reach a global audience. 50 | - Easily add new translations or override existing ones. 51 | 52 | ## 🎯 Benefits 53 | 54 | 1. **Reduced Development Time**: Streamline form creation and validation with minimal boilerplate code. 55 | 2. **Enhanced User Experience**: Provide instant feedback with real-time validation and clear error messages. 56 | 3. **Flexibility**: Easily adapt to changing requirements with dynamic field management and extensible architecture. 57 | 4. **Powerful Validation**: Combine schema-based and custom validators to handle complex validation scenarios. 58 | 5. **Asynchronous Support**: Seamlessly integrate server-side validations without blocking the UI. 59 | 6. **Event-Driven Architecture**: Extend functionality with hooks and custom event listeners. 60 | 7. **Customizable UI**: Tailor error messages and styles to match your application's design. 61 | 8. **Internationalization**: Support multiple languages for a global reach. 62 | 63 | ## 📦 Installation 64 | 65 | ### Using npm 66 | 67 | Install the package via npm: 68 | 69 | ```bash 70 | npm install shadow-form-handler 71 | ``` 72 | 73 | ### Using HTML Script Tag 74 | 75 | To use **ShadowFormHandler** directly in your HTML file, add the following script tag: 76 | 77 | ```html 78 | 79 | ``` 80 | 81 | ### Using CDN 82 | 83 | Alternatively, you can use **ShadowFormHandler** via a CDN by adding this script tag: 84 | 85 | ```html 86 | 87 | ``` 88 | 89 | ### Using GitHub Releases 90 | 91 | You can also download the latest release directly from GitHub. Follow these steps: 92 | 93 | 1. **Navigate to the Releases Page**: 94 | Go to the [Releases](https://github.com/Mostafashadow1/shadow-form-handler/releases) section of your GitHub repository. 95 | 96 | 2. **Download the Latest Release**: 97 | Find the latest release and download the `shadow-form-handler.js` file from the assets section. 98 | 99 | 3. **Include the Script in Your HTML**: 100 | Add the downloaded file to your project and include it in your HTML file like this: 101 | 102 | ```html 103 | 104 | ``` 105 | 106 | 107 | ### Simple Form Handling 108 | ```html 109 | 110 | 111 | 112 | 113 | 114 | Simple Example with shadow-form-handler 115 | 116 | 117 | 118 |
119 |
120 | 121 | 122 | 123 |
124 | 125 |
126 | 127 | 128 | 129 | 130 | 131 | 132 | ``` 133 | 134 | ```javascript 135 | const { FormHandler } = shadowFormHandler; 136 | const formHandler = new FormHandler(); 137 | 138 | // Register a field 139 | formHandler.register({ 140 | id: 'username', 141 | initialValue: "", 142 | schemaValidation: [ 143 | formHandler.validation.required(), // if no message ? show default message 144 | formHandler.validation.minLength(3, 'Username must be at least 3 characters long'), 145 | formHandler.validation.maxLength(15, 'Username must be less than 15 characters'), 146 | ] 147 | }); 148 | 149 | // Set up form submission 150 | formHandler.submitHandler('myForm', (values) => { 151 | console.log('Form submitted with values:', values); 152 | }); 153 | ``` 154 | 155 | 156 | ## Core Components 157 | 158 | ## Methods 159 | 160 | ### Form Field Methods 161 | 162 | - **`register({id: string, initialValue?: string, schemaValidation?: [], customValidation?:[] })`**: 163 | - **Purpose**: Register a new form field. 164 | - **Usage**: 165 | ```typescript 166 | formHandler.register({ id: 'username', initialValue: '', schemaValidation: [], customValidation: [] }); 167 | ``` 168 | 169 | - **`setMode(newMode: 'default' || 'runtime')`**: 170 | - **Purpose**: Set the validation mode (e.g., Default, Runtime). 171 | - **Usage**: 172 | ```typescript 173 | formHandler.setMode('runtime'); 174 | ``` 175 | 176 | - **`submitHandler(formId: string, onSubmit?: Function)`**: 177 | - **Purpose**: Handle form submission. 178 | - **Usage**: 179 | ```typescript 180 | formHandler.submitHandler('myForm', (data) => console.log(data)); 181 | ``` 182 | 183 | - **`getValue(id: string)`**: 184 | - **Purpose**: Get the value of a specific field. 185 | - **Usage**: 186 | ```typescript 187 | const username = formHandler.getValue('username'); 188 | ``` 189 | 190 | - **`getValues()`**: 191 | - **Purpose**: Get values of all fields. 192 | - **Usage**: 193 | ```typescript 194 | const allValues = formHandler.getValues(); 195 | ``` 196 | 197 | - **`resetForm()`**: 198 | - **Purpose**: Reset all form fields and errors. 199 | - **Usage**: 200 | ```typescript 201 | formHandler.resetForm(); 202 | ``` 203 | 204 | - **`removeField({ fieldId: string, disabledId?: string })`**: 205 | - **Purpose**: Remove a field dynamically. 206 | - **Usage**: 207 | ```typescript 208 | formHandler.removeField({ fieldId: 'descraption'}); 209 | ``` 210 | 211 | - **`addField({containerId: string, fieldId: string, labelText: string, register?: RegisterParams, position?: "top" | "bottom" | number, disabledId?: string })`**: 212 | - **Purpose**: Add a new field dynamically. 213 | - **Usage**: 214 | ```typescript 215 | formHandler.addField({ containerId: 'formContainer', fieldId: 'password', labelText: 'Password', register: { id: 'password', validation: 'required|min:8' }, position: 'bottom' }); 216 | ``` 217 | 218 | - **`setErrorStyles(style: ErrorStyle)`**: 219 | - **Purpose**: Set custom error styles. 220 | - **Usage**: 221 | ```typescript 222 | formHandler.setErrorStyles({ color: 'red', fontSize: '12px' }); 223 | ``` 224 | 225 | Sure! Here's the updated section for your README file: 226 | 227 | ### Methods 228 | 229 | #### `addHooks(hooks: Hooks)` 230 | 231 | **Purpose**: Add custom hooks for form lifecycle events. 232 | **Usage**: 233 | ```typescript 234 | formHandler.addHooks({ 235 | onFormSubmitSuccess: (values) => { 236 | console.log("Form submitted successfully with values:", values); 237 | }, 238 | onFormSubmitFail: (errors) => { 239 | console.error("Form submission failed with errors:", errors); 240 | }, 241 | onFieldValidationError: (fieldId, error) => { 242 | if (error) { 243 | console.warn(`Validation error on field ${fieldId}: ${error}`); 244 | } 245 | }, 246 | onFormReset: () => { 247 | console.log("Form reset successfully"); 248 | }, 249 | beforeFieldRegister: (fieldId, params) => { 250 | console.log(`Registering field: ${fieldId}`, params); 251 | }, 252 | afterFieldRegister: (fieldId, params) => { 253 | console.log(`Field registered: ${fieldId}`, params); 254 | }, 255 | onValidationStart: (fields) => { 256 | console.log("Validation started", fields); 257 | }, 258 | onValidationEnd: (fields) => { 259 | console.log("Validation ended", fields); 260 | }, 261 | onValueChange: (fieldId, value) => { 262 | console.log(fieldId, value); 263 | }, 264 | onFieldAdd: (fieldId) => { 265 | console.log(fieldId); 266 | }, 267 | onFieldRemove: (fieldId) => { 268 | console.log(fieldId); 269 | }, 270 | onFocus: (field) => { 271 | console.log(field); 272 | }, 273 | onBlur: (field) => { 274 | console.log(field); 275 | }, 276 | }); 277 | ``` 278 | 279 | - **`addCustomEventListener(id: string, event: string, listener: EventListener)`**: 280 | - **Purpose**: Add a custom event listener to a field. 281 | - **Usage**: 282 | ```typescript 283 | formHandler.addCustomEventListener('username', 'focus', () => console.log('Username field focused')); 284 | ``` 285 | 286 | ### Language Methods 287 | 288 | - **`setCurrentLanguage(langCode: string)`**: 289 | - **Purpose**: Set the current language. 290 | - **Usage**: 291 | ```typescript 292 | formHandler.lang.setCurrentLanguage('ar'); 293 | ``` 294 | 295 | - **`getCurrentLanguage()`**: 296 | - **Purpose**: Get the current language. 297 | - **Usage**: 298 | ```typescript 299 | const currentLang = formHandler.lang.getCurrentLanguage(); 300 | ``` 301 | 302 | - **`addTranslations(lang: string, translations: { [key: string]: string; })`**: 303 | - **Purpose**: Add translations for a specific language. 304 | - **Usage**: 305 | ```typescript 306 | formHandler.lang.addTranslations('en', { 307 | 'password': 'Password must contain at least one uppercase letter, one lowercase letter, and one number.', 308 | 'confirmPassword' : "Passwords do not match" 309 | }); 310 | 311 | formHandler.lang.addTranslations('ar', { 312 | 'password': 'يجب أن تحتوي كلمة المرور على حرف كبير واحد على الأقل، وحرف صغير واحد، ورقم واحد.', 313 | 'confirmPassword' : "كلمات المرور غير متطابقة." 314 | }); 315 | 316 | formHandler.lang.addTranslations('es', { 317 | 'password': 'Le mot de passe doit contenir au moins une lettre majuscule, une lettre minuscule et un chiffre.', 318 | 'confirmPassword' : "Les mots de passe ne correspondent pas." 319 | }); 320 | ``` 321 | 322 | ```typescript 323 | const currentLang = formHandler.lang.getCurrentLanguage(); 324 | ``` 325 | 326 | 327 | 328 | 329 | ### Built-in Validation Rules 330 | 331 | The package provides the following validation rules: 332 | 333 | 1. **`required(message?: string)`**: 334 | - Ensures the field is not empty. 335 | - If no `message` is provided, it will use the translation for the 'required' key, or the default message `"This field is required."`. 336 | 337 | 2. **`minLength(min: number, message?: string)`**: 338 | - Checks if the value has at least the minimum length. 339 | - If no `message` is provided, it will use the translation for the 'minLength' key, or the default message `"This field must be at least {min} characters long."`. 340 | 341 | 3. **`maxLength(max: number, message?: string)`**: 342 | - Checks if the value doesn't exceed the maximum length. 343 | - If no `message` is provided, it will use the translation for the 'maxLength' key, or the default message `"This field must be no more than {max} characters long."`. 344 | 345 | 4. **`pattern(regex: RegExp, message?: string)`**: 346 | - Validates the value against a regular expression pattern. 347 | - If no `message` is provided, it will use the translation for the 'pattern' key, or the default message `"This field does not match the required pattern."`. 348 | 349 | 5. **`email(message?: string)`**: 350 | - Checks if the value is a valid email format. 351 | - If no `message` is provided, it will use the translation for the 'email' key, or the default message `"This field must be a valid email address."`. 352 | 353 | 6. **`range(min: number, max: number, message?: string)`**: 354 | - Ensures the value is within a specified range. 355 | - If no `message` is provided, it will use the translation for the 'range' key, or the default message `"The value must be between {min} and {max}."`. 356 | 357 | 7. **`matches(fieldToMatch: string, message?: string)`**: 358 | - Ensures the value matches another field's value. 359 | - If no `message` is provided, it will use the translation for the 'matches' key, or the default message `"This field must match the {fieldToMatch} field."`. 360 | 361 | The validation rules use the `lang.getTranslation()` function to retrieve the appropriate error message. If the translation key is not found, the default message will be used. 362 | 363 | ### Dynamic Field Management 364 | 365 | ```javascript 366 | // Add a new field 367 | formHandler.addField({ 368 | containerId: 'formContainer', 369 | fieldId: 'newField', 370 | labelText: 'New Field', 371 | register: { 372 | initialValue: '', 373 | schemaValidation: [required('This field is required')] 374 | }, 375 | position: 'top' // position of element added: 'top' | 'bottom' | number of element filed like 3 376 | }); 377 | 378 | // Remove a field 379 | formHandler.removeField({ 380 | fieldId: 'fieldToRemove', 381 | disabledId: 'addButton' 382 | }); 383 | ``` 384 | 385 | ### Asynchronous Validation 386 | 387 | ```javascript 388 | formHandler.register({ 389 | id: 'username', 390 | customValidation: [{ 391 | validate: async (value) => { 392 | // Simulating an API call to check username uniqueness 393 | await new Promise(resolve => setTimeout(resolve, 1000)); 394 | return !value.includes('shadow' && 'mostafa mohamed' && 'mostafa'); 395 | }, 396 | message: 'This username is already taken.', 397 | }] 398 | }); 399 | ``` 400 | 401 | ### Internationalization and Localization 402 | 403 | ```javascript 404 | 405 | // Set the current language 406 | formHandler.lang.setCurrentLanguage('ar'); 407 | 408 | // Add translations for different languages 409 | formHandler.lang.addTranslations('en', { 410 | 'required': 'This field is required.', 411 | 'minLength': 'This field must be at least {min} characters long.', 412 | 'maxLength': 'This field must not exceed {max} characters.', 413 | 'email': 'Please enter a valid email address.', 414 | 'range': 'Value must be between {min} and {max}.', 415 | 'matches': 'This field must match {fieldToMatch}.', 416 | }); 417 | 418 | formHandler.lang.addTranslations('ar', { 419 | 'required': 'هذا الحقل مطلوب.', 420 | 'minLength': 'يجب أن يحتوي هذا الحقل على {min} أحرف على الأقل.', 421 | 'maxLength': 'يجب ألا يتجاوز هذا الحقل {max} حرفًا.', 422 | 'email': 'يرجى إدخال عنوان بريد إلكتروني صالح.', 423 | 'range': 'يجب أن تكون القيمة بين {min} و {max}.', 424 | 'matches': 'يجب أن يتطابق هذا الحقل مع {fieldToMatch}.', 425 | }); 426 | 427 | ``` 428 | 429 | ### Dependent Field Validation With built in 'matches' method 430 | 431 | ```javascript 432 | 433 | formHandler.lang.addTranslations('en', { 434 | 'password': 'Password must contain at least one uppercase letter, one lowercase letter, and one number.', 435 | 'confirmPassword' : "Passwords do not match" 436 | 437 | }); 438 | 439 | formHandler.lang.addTranslations('ar', { 440 | 'password': 'يجب أن تحتوي كلمة المرور على حرف كبير واحد على الأقل، وحرف صغير واحد، ورقم واحد.', 441 | 'confirmPassword' : "كلمات المرور غير متطابقة." 442 | 443 | }); 444 | 445 | formHandler.lang.addTranslations('es', { 446 | 'password': 'Le mot de passe doit contenir au moins une lettre majuscule, une lettre minuscule et un chiffre.', 447 | 'confirmPassword' : "Les mots de passe ne correspondent pas." 448 | }); 449 | 450 | formHandler.register({ 451 | id: 'password', 452 | schemaValidation: [ 453 | formHandler.validation.required(), 454 | formHandler.validation.minLength(8), 455 | formHandler.validation.pattern(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/, formHandler.lang.getTranslation('password')) 456 | ] 457 | }); 458 | 459 | formHandler.register({ 460 | id: 'confirm-password', 461 | schemaValidation: [ 462 | formHandler.validation.required(), 463 | formHandler.validation.matches('password' , formHandler.lang.getTranslation('confirmPassword')) 464 | ] 465 | }); 466 | 467 | ``` 468 | ### in this approach we prefer uses dependencies 469 | ```javascript 470 | formHandler.register({ 471 | id: 'confirm-password', 472 | dependencies: ['password'], 473 | customValidation: [{ 474 | validate: (value, formValues) => value === formValues.password, 475 | message: formHandler.lang.getTranslation('confirmPassword') 476 | }] 477 | }); 478 | ``` 479 | 480 | 481 | 482 | ### Hooks System 483 | 484 | - **`onFormSubmitSuccess(values)`**: Triggered when the form is successfully submitted. 485 | - **Parameters**: 486 | - `values`: The values submitted in the form. 487 | - **Example**: 488 | ```typescript 489 | onFormSubmitSuccess: (values) => { 490 | console.log("Form submitted successfully with values:", values); 491 | } 492 | ``` 493 | 494 | - **`onFormSubmitFail(errors)`**: Triggered when the form submission fails. 495 | - **Parameters**: 496 | - `errors`: The errors encountered during submission. 497 | - **Example**: 498 | ```typescript 499 | onFormSubmitFail: (errors) => { 500 | console.error("Form submission failed with errors:", errors); 501 | } 502 | ``` 503 | 504 | - **`onFieldValidationError(fieldId, error)`**: Triggered when a field validation error occurs. 505 | - **Parameters**: 506 | - `fieldId`: The ID of the field with the error. 507 | - `error`: The validation error message. 508 | - **Example**: 509 | ```typescript 510 | onFieldValidationError: (fieldId, error) => { 511 | if (error) { 512 | console.warn(`Validation error on field ${fieldId}: ${error}`); 513 | } 514 | } 515 | ``` 516 | 517 | - **`onFormReset()`**: Triggered when the form is reset. 518 | - **Example**: 519 | ```typescript 520 | onFormReset: () => { 521 | console.log("Form reset successfully"); 522 | } 523 | ``` 524 | 525 | - **`beforeFieldRegister(fieldId, params)`**: Triggered before a field is registered. 526 | - **Parameters**: 527 | - `fieldId`: The ID of the field being registered. 528 | - `params`: The parameters for the field registration. 529 | - **Example**: 530 | ```typescript 531 | beforeFieldRegister: (fieldId, params) => { 532 | console.log(`Registering field: ${fieldId}`, params); 533 | } 534 | ``` 535 | 536 | - **`afterFieldRegister(fieldId, params)`**: Triggered after a field is registered. 537 | - **Parameters**: 538 | - `fieldId`: The ID of the field being registered. 539 | - `params`: The parameters for the field registration. 540 | - **Example**: 541 | ```typescript 542 | afterFieldRegister: (fieldId, params) => { 543 | console.log(`Field registered: ${fieldId}`, params); 544 | } 545 | ``` 546 | 547 | - **`onValidationStart(fields)`**: Triggered when validation starts. 548 | - **Parameters**: 549 | - `fields`: The fields being validated. 550 | - **Example**: 551 | ```typescript 552 | onValidationStart: (fields) => { 553 | console.log("Validation started", fields); 554 | } 555 | ``` 556 | 557 | - **`onValidationEnd(fields)`**: Triggered when validation ends. 558 | - **Parameters**: 559 | - `fields`: The fields that were validated. 560 | - **Example**: 561 | ```typescript 562 | onValidationEnd: (fields) => { 563 | console.log("Validation ended", fields); 564 | } 565 | ``` 566 | 567 | - **`onValueChange(fieldId, value)`**: Triggered when a field value changes. 568 | - **Parameters**: 569 | - `fieldId`: The ID of the field. 570 | - `value`: The new value of the field. 571 | - **Example**: 572 | ```typescript 573 | onValueChange: (fieldId, value) => { 574 | console.log(fieldId, value); 575 | } 576 | ``` 577 | 578 | - **`onFieldAdd(fieldId)`**: Triggered when a field is added. 579 | - **Parameters**: 580 | - `fieldId`: The ID of the field being added. 581 | - **Example**: 582 | ```typescript 583 | onFieldAdd: (fieldId) => { 584 | console.log(fieldId); 585 | } 586 | ``` 587 | 588 | - **`onFieldRemove(fieldId)`**: Triggered when a field is removed. 589 | - **Parameters**: 590 | - `fieldId`: The ID of the field being removed. 591 | - **Example**: 592 | ```typescript 593 | onFieldRemove: (fieldId) => { 594 | console.log(fieldId); 595 | } 596 | ``` 597 | 598 | - **`onFocus(field)`**: Triggered when a field gains focus. 599 | - **Parameters**: 600 | - `field`: The field that gained focus. 601 | - **Example**: 602 | ```typescript 603 | onFocus: (field) => { 604 | console.log(field); 605 | } 606 | ``` 607 | 608 | - **`onBlur(field)`**: Triggered when a field loses focus. 609 | - **Parameters**: 610 | - `field`: The field that lost focus. 611 | - **Example**: 612 | ```typescript 613 | onBlur: (field) => { 614 | console.log(field); 615 | } 616 | ``` 617 | 618 | 619 | 620 | ## 💡 Usage Example 621 | 622 | Here's a comprehensive example demonstrating the power of shadow-form-handler: 623 | 624 | ```html 625 | 626 | 627 | 628 | 629 | 630 | Enhanced Form Validation Example 631 | 632 | 633 | 634 | 635 |
636 | 637 |
638 | 639 | 640 | 641 |
642 |
643 | 644 | 645 | 646 |
647 |
648 | 649 | 650 | 651 |
652 |
653 | 654 | 655 | 656 | 657 |
658 |
659 | 660 | 661 | 662 |
663 |
664 | 665 | 666 | 667 |
668 |
669 | 670 | 671 | 672 |
673 | 674 | 675 | 676 | 677 |
678 | 679 | 680 | 681 | 682 | 683 | 684 | ``` 685 | 686 | ```javascript 687 | const { FormHandler} = shadowFormHandler; 688 | const formHandler = new FormHandler(); 689 | formHandler.setMode('runtime'); // runtime or default 690 | 691 | formHandler.lang.getCurrentLanguage(); 692 | // Add translations 693 | formHandler.lang.addTranslations('en', { 694 | 'required': 'This field is required.', 695 | 'minLength': 'This field must be at least {min} characters long.', 696 | 'maxLength': 'This field must not exceed {max} characters.', 697 | 'email': 'Please enter a valid email address.', 698 | 'range': 'Value must be between {min} and {max}.', 699 | 'matches': 'This field must match {fieldToMatch}.', 700 | }); 701 | 702 | formHandler.lang.addTranslations('ar', { 703 | 'required': 'هذا الحقل مطلوب.', 704 | 'minLength': 'يجب أن يحتوي هذا الحقل على {min} أحرف على الأقل.', 705 | 'maxLength': 'يجب ألا يتجاوز هذا الحقل {max} حرفًا.', 706 | 'email': 'يرجى إدخال عنوان بريد إلكتروني صالح.', 707 | 'range': 'يجب أن تكون القيمة بين {min} و {max}.', 708 | 'matches': 'يجب أن يتطابق هذا الحقل مع {fieldToMatch}.', 709 | }); 710 | 711 | formHandler.lang.addTranslations('es', { 712 | 'required': 'Este campo es obligatorio.', 713 | 'minLength': 'Este campo debe tener al menos {min} caracteres.', 714 | 'maxLength': 'Este campo no debe exceder los {max} caracteres.', 715 | 'email': 'Por favor, introduce una dirección de correo electrónico válida.', 716 | 'range': 'El valor debe estar entre {min} y {max}.', 717 | 'matches': 'Este campo debe coincidir con {fieldToMatch}.', 718 | }); 719 | 720 | 721 | formHandler.lang.setCurrentLanguage('ar') 722 | 723 | formHandler.setErrorStyles({ 724 | color: "red", 725 | marginTop:2, 726 | }) 727 | formHandler.register({ 728 | id: "firstname", 729 | initialValue: "", 730 | schemaValidation: [ 731 | formHandler.validation.required(), 732 | formHandler.validation.maxLength(10), 733 | formHandler.validation.minLength(4) 734 | ] 735 | }); 736 | formHandler.register({ 737 | id: "lastname", 738 | initialValue: "", 739 | schemaValidation: [ 740 | formHandler.validation.required(), 741 | formHandler.validation.maxLength(10), 742 | formHandler.validation.minLength(4) 743 | ] 744 | }); 745 | 746 | formHandler.lang.addTranslations('en', { 747 | 'usernameTaken': 'This username is already taken.' 748 | }); 749 | 750 | formHandler.lang.addTranslations('ar', { 751 | 'usernameTaken': 'اسم المستخدم هذا مستخدم بالفعل.' 752 | }); 753 | 754 | formHandler.lang.addTranslations('es', { 755 | 'usernameTaken': 'Este nombre de usuario ya está en uso.' 756 | }); 757 | 758 | formHandler.register({ 759 | id: "username", 760 | dependencies: ['email'], 761 | schemaValidation: [formHandler.validation.required() , formHandler.validation.minLength(3), 762 | formHandler.validation.maxLength(20)], 763 | customValidation: [ 764 | { 765 | validate: async (value) => { 766 | // Simulate an API call to check if the email is already registered 767 | await new Promise(resolve => setTimeout(resolve, 1000)); 768 | return !value.includes('shadow' && 'mostafa mohamed' && 'mostafa'); 769 | }, 770 | message: formHandler.lang.getTranslation('usernameTaken'), 771 | } 772 | ] 773 | }); 774 | 775 | 776 | 777 | formHandler.register({ 778 | id: 'email', 779 | schemaValidation: [ 780 | formHandler.validation.required(), 781 | formHandler.validation.email() 782 | ] 783 | }); 784 | 785 | formHandler.lang.addTranslations('en', { 786 | 'password': 'Password must contain at least one uppercase letter, one lowercase letter, and one number.', 787 | 'confirmPassword' : "Passwords do not match" 788 | 789 | }); 790 | 791 | formHandler.lang.addTranslations('ar', { 792 | 'password': 'يجب أن تحتوي كلمة المرور على حرف كبير واحد على الأقل، وحرف صغير واحد، ورقم واحد.', 793 | 'confirmPassword' : "كلمات المرور غير متطابقة." 794 | 795 | }); 796 | 797 | formHandler.lang.addTranslations('es', { 798 | 'password': 'Le mot de passe doit contenir au moins une lettre majuscule, une lettre minuscule et un chiffre.', 799 | 'confirmPassword' : "Les mots de passe ne correspondent pas." 800 | }); 801 | 802 | formHandler.register({ 803 | id: 'password', 804 | schemaValidation: [ 805 | formHandler.validation.required(), 806 | formHandler.validation.minLength(8), 807 | formHandler.validation.pattern(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/, formHandler.lang.getTranslation('password')) 808 | ] 809 | }); 810 | 811 | 812 | formHandler.register({ 813 | id: 'confirm-password', 814 | schemaValidation: [ 815 | formHandler.validation.required(), 816 | formHandler.validation.matches('password' , formHandler.lang.getTranslation('confirmPassword')) 817 | ] 818 | }); 819 | 820 | formHandler.register({ 821 | id: 'confirm-password', 822 | dependencies: ['password'], 823 | customValidation: [{ 824 | validate: (value, formValues) => value === formValues.password, 825 | message: formHandler.lang.getTranslation('confirmPassword') 826 | }] 827 | }); 828 | 829 | formHandler.register({ 830 | id: 'age', 831 | schemaValidation: [ 832 | formHandler.validation.required(), 833 | formHandler.validation.range(18, 100) 834 | ] 835 | }); 836 | 837 | // Adding hooks for validation events 838 | formHandler.addHooks({ 839 | onFormSubmitSuccess: (values) => { 840 | console.log("Form submitted successfully with values:", values); 841 | }, 842 | onFormSubmitFail: (errors) => { 843 | console.error("Form submission failed with errors:", errors); 844 | }, 845 | onFieldValidationError: (fieldId, error) => { 846 | if(error) { 847 | console.warn(`Validation error on field ${fieldId}: ${error}`); 848 | } 849 | }, 850 | onFormReset: () => { 851 | console.log(`form reseted susccfully`); 852 | }, 853 | beforeFieldRegister: (fieldId, params) => { 854 | console.log(`Registering field: ${fieldId}`, params); 855 | }, 856 | afterFieldRegister: (fieldId, params) => { 857 | console.log(`Field registered: ${fieldId}`, params); 858 | }, 859 | onValidationStart: (fields) => { 860 | console.log( "beforeValidate", fields), 861 | }, 862 | onValidationEnd:(fields) => { 863 | console.log("after validated" , fields) 864 | }, 865 | onValueChange: (fieldId, value) => { 866 | console.log(fieldId , value) 867 | }, 868 | onFieldAdd:(fieldId) => { 869 | console.log(fieldId) 870 | }, 871 | onFieldRemove:(fieldId) => { 872 | console.log(fieldId) 873 | }, 874 | onFocus:(field) => { 875 | console.log(field) 876 | }, 877 | onBlur:(field) => { 878 | console.log(field) 879 | }, 880 | }) 881 | 882 | 883 | // Adding custom event listener 884 | formHandler.addCustomEventListener("email", "blur", (event) => { 885 | console.log("Email input lost focus", event); 886 | }); 887 | 888 | // Handling form submission 889 | formHandler.submitHandler('myForm', (values) => { 890 | console.log('Form submitted with values:', values); 891 | }); 892 | 893 | // Adding a field dynamically 894 | document.getElementById('addDescription').addEventListener('click', () => { 895 | formHandler.addField({ 896 | containerId:'myForm', 897 | fieldId:'description', 898 | labelText:'Description', 899 | register:{ 900 | initialValue:'', 901 | schemaValidation:[formHandler.validation.required()], 902 | }, 903 | position:3, 904 | disabledId:"addDescription", 905 | register:{ 906 | initialValue: "", 907 | schemaValidation: [ 908 | formHandler.validation.required(), 909 | formHandler.validation.maxLength(10), 910 | formHandler.validation.minLength(4) 911 | ], 912 | customValidation:[] 913 | } 914 | }) 915 | }); 916 | 917 | // Removing a field dynamically 918 | document.getElementById('removeUsername').addEventListener('click', () => { 919 | formHandler.removeField( {fieldId : "username" , disabledId:"removeUsername"} ); 920 | }); 921 | 922 | 923 | // Removing a field dynamically 924 | document.getElementById('resetform').addEventListener('click', () => { 925 | formHandler.resetForm() 926 | }); 927 | ``` 928 | ## 🛠️ Best Practices 929 | 930 | 1. **Clear Error Messages**: Provide clear and specific error messages for each validation rule to enhance the user's understanding. 931 | 2. **Asynchronous Validations**: Use asynchronous validations sparingly to avoid performance issues. Ensure they are necessary and optimized. 932 | 3. **Error Handling**: Implement proper error handling for asynchronous operations to manage potential issues gracefully. 933 | 4. **Utilize Hooks**: Leverage hooks to extend functionality without modifying the core logic, ensuring maintainability and flexibility. 934 | 5. **Regular Resets**: Regularly reset the form to clear old data and errors, maintaining a fresh state for new submissions. 935 | 936 | ## 🔧 Troubleshooting 937 | 938 | - **Validation Issues**: If fields are not being validated, ensure they are properly registered with the correct validation rules. 939 | - **Runtime Validation**: For runtime validation issues, verify that the correct mode (`default` or `runtime`) is set. 940 | - **Event Listeners**: If custom event listeners are not firing, check the element IDs and event types to ensure they are correctly specified. 941 | 942 | ## 🤝 Contributing 943 | 944 | Contributions to the `shadow-form-handler` package are welcome! Please refer to the contributing guidelines in the repository for more information on how to submit pull requests, report issues, or request features. 945 | 946 | ## 📄 License 947 | 948 | This package is released under the MIT License. See the LICENSE file in the package repository for more details. -------------------------------------------------------------------------------- /src/cors/ErrorManager.ts: -------------------------------------------------------------------------------- 1 | import { Field , ErrorStyle } from '../interfaces/index'; 2 | import { FieldManager } from './FieldManager'; 3 | 4 | 5 | /** 6 | * ErrorManager class: Manages error display and styling. 7 | */ 8 | export class ErrorManager { 9 | constructor(private fieldManager : FieldManager) {} 10 | private errorStyles: ErrorStyle = { property : " " , value : ""} 11 | 12 | /** 13 | * Set error styles to be applied to error elements. 14 | * @param {ErrorStyle} style - An error style object to be applied. 15 | */ 16 | public setErrorStyles(style: ErrorStyle) { 17 | this.errorStyles =style; 18 | } 19 | 20 | 21 | /** 22 | * Apply the error styles to a given element. 23 | * @param element - The element to apply styles to. 24 | */ 25 | public applyStyles(element: HTMLElement) { 26 | for (const [property, value] of Object.entries(this.errorStyles)) { 27 | (element.style as any)[property] = value; 28 | } 29 | } 30 | 31 | /** 32 | * Display errors for all form fields. 33 | * @param {Field[]} fields - An array of all form fields. 34 | */ 35 | public displayErrors(fields: Field[]) { 36 | fields.forEach(field => { 37 | const errorElement = document.getElementById(`${field.id}-error`); 38 | if (errorElement) { 39 | errorElement.textContent = field.error || ''; 40 | this.applyStyles(errorElement); 41 | } 42 | }); 43 | } 44 | 45 | /** 46 | * Clear all error messages. 47 | */ 48 | public clearErrors() { 49 | const errorElements = document.querySelectorAll('.error-message'); 50 | errorElements.forEach(element => { 51 | (element as HTMLElement).textContent = ''; 52 | }); 53 | } 54 | 55 | /** 56 | * Check if any field has errors. 57 | * @returns {boolean} True if any field has an error, false otherwise. 58 | */ 59 | public hasErrors(): boolean { 60 | const fields = this.fieldManager.getAllFields(); 61 | return fields.some(field => field.error !== null); 62 | } 63 | 64 | /** 65 | * get all errors and return it 66 | * @returns {Array} get all erros 67 | */ 68 | public getErrors() : {}{ 69 | const fields = this.fieldManager.getAllFields(); 70 | return fields.map(field => { 71 | return { 72 | [field.id] : field.error 73 | } 74 | }) 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /src/cors/EventManger.ts: -------------------------------------------------------------------------------- 1 | 2 | import { FieldManager } from './FieldManager'; 3 | import { ValidatorManager } from './ValidatorManager'; 4 | import { HooksHandler } from './HooksHandler'; 5 | 6 | /** 7 | * EventManager class: Manages form-related events and hooks. 8 | */ 9 | export class EventManager { 10 | constructor( 11 | private fieldManager: FieldManager, 12 | private validator: ValidatorManager, 13 | private hooksHandler : HooksHandler 14 | 15 | ) {} 16 | 17 | 18 | 19 | /** 20 | * Add runtime validation to a form field. 21 | * @param {string} id - The id of the form field. 22 | */ 23 | public addRuntimeValidation(id: string) { 24 | const inputElement = document.getElementById(id) as HTMLInputElement; 25 | if (inputElement) { 26 | inputElement.addEventListener('input', this.handleInputChange.bind(this)); 27 | } 28 | } 29 | 30 | /** 31 | * Handle input change event. 32 | * @param {Event} event - The input change event. 33 | */ 34 | private async handleInputChange(event: Event) { 35 | const target = event.target as HTMLInputElement; 36 | const field = this.fieldManager.getField(target.id); 37 | if(!field) return; 38 | field.value = target.value; 39 | this.hooksHandler.triggerHook('onValueChange' , field.id , field.value) 40 | await this.validator.validateField(field.id); 41 | 42 | } 43 | 44 | /** 45 | * Add a custom event listener to a form field. 46 | * @param {string} id - The id of the form field. 47 | * @param {string} event - The event type to listen for. 48 | * @param {EventListener} listener - The event listener function. 49 | */ 50 | public addCustomEventListener(id: string, event: string, listener: EventListener) { 51 | const inputElement = document.getElementById(id) as HTMLInputElement; 52 | if (!inputElement) { 53 | console.error(`Input element with id "${id}" not found.`); 54 | return; 55 | } 56 | inputElement.addEventListener(event, listener); 57 | } 58 | } -------------------------------------------------------------------------------- /src/cors/FieldManager.ts: -------------------------------------------------------------------------------- 1 | import { addFieldParams, Field, RegisterParams } from '../interfaces/index'; 2 | 3 | /** 4 | * FieldManager class: Manages form fields, their values, and related DOM operations. 5 | */ 6 | export class FieldManager { 7 | private fields: Field[] = []; 8 | 9 | /** 10 | * Register a new form field. 11 | * @param {RegisterParams} params - The parameters for registering a field. 12 | */ 13 | public register(params: RegisterParams) { 14 | const { id, schemaValidation, customValidation, initialValue , dependencies} = params; 15 | this.fields.push({ id, value: initialValue || '', error: null, schemaValidation, customValidation , dependencies}); 16 | const inputElement = document.getElementById(id) as HTMLInputElement; 17 | if (!inputElement) throw new Error(`Input element with id ${id} not found`); 18 | inputElement.addEventListener('input' , ({target}) => { 19 | this.setValue(params.id , (target as HTMLInputElement).value) 20 | }); 21 | if (initialValue) { 22 | inputElement.value = initialValue; 23 | } 24 | 25 | } 26 | 27 | 28 | 29 | /** 30 | * Dynamically remove a form field. 31 | * @param {string} fieldId - The id of the form field. 32 | * @param {string} disabledId -The ID of a button element that should be disabled when the field is added. 33 | */ 34 | public removeField(params : {fieldId : string , disabledId:string}) { 35 | const inputElement = document.getElementById(params.fieldId) as HTMLInputElement; 36 | if (!inputElement) { 37 | console.error(`Element with id ${params.fieldId} not found.`); 38 | return; 39 | } 40 | // Handle the button state if buttonId is provided 41 | if (params.disabledId) { 42 | const button = document.getElementById(params.disabledId) as HTMLButtonElement; 43 | if (button) { 44 | button.disabled = true; 45 | } 46 | } 47 | this.fields = this.fields.filter(f => f.id !== params.fieldId); 48 | const errorElement = document.getElementById(`${params.fieldId}-error`); 49 | const label = document.querySelector(`label[for="${params.fieldId}"]`); 50 | 51 | inputElement.remove(); 52 | errorElement?.remove(); 53 | label?.remove(); 54 | 55 | } 56 | 57 | /** 58 | * Add a new form field to the DOM and register it. 59 | * @param {Object} params - Parameters for adding a new field. 60 | * @param {string} params.containerId - The ID of the container element where the field should be added. 61 | * @param {string} params.fieldId - The ID of the new form field. 62 | * @param {string} params.labelText - The text to display as the label for the form field. 63 | * @param {RegisterParams} [params.register] - Optional parameters for registering the field. 64 | * @param {string|number} [params.position] - The position where the new field should be inserted. 65 | * @param {string} [params.disabledId] - The ID of a button element that should be disabled when the field is added. 66 | */ 67 | public addField(params:addFieldParams) { 68 | // Check if an input with the same ID already exists 69 | const existingInput = document.getElementById(params.fieldId) as HTMLInputElement; 70 | if (existingInput) { 71 | console.warn(`Field with id ${params.fieldId} already exists. Skipping creation.`); 72 | return; 73 | } 74 | // Handle the button state if buttonId is provided 75 | if (params.disabledId) { 76 | const button = document.getElementById(params.disabledId) as HTMLButtonElement; 77 | if (button) { 78 | button.disabled = true; 79 | } 80 | } 81 | const container = document.getElementById(params.containerId); 82 | if (!container) throw new Error(`Container element with id ${params.containerId} not found`); 83 | 84 | const fieldWrapper = document.createElement('div'); 85 | const label = document.createElement('label'); 86 | const input = document.createElement('input'); 87 | const errorElement = document.createElement('span'); 88 | 89 | label.setAttribute('for', params.fieldId); 90 | label.textContent = params.labelText; 91 | input.setAttribute('id', params.fieldId); 92 | input.setAttribute('name', params.fieldId); 93 | input.setAttribute('type', 'text'); 94 | errorElement.setAttribute('id', `${params.fieldId}-error`); 95 | errorElement.classList.add('error-message'); 96 | 97 | fieldWrapper.appendChild(label); 98 | fieldWrapper.appendChild(input); 99 | fieldWrapper.appendChild(errorElement); 100 | if(!params.position) container.appendChild(fieldWrapper) 101 | else if(params.position === "top") { 102 | container.insertBefore(fieldWrapper, container.firstChild) 103 | } else if(params.position === "bottom") { 104 | container.appendChild(fieldWrapper) 105 | }else if(typeof params.position === "number" && params.position >= 0) { 106 | const existingElement = container.children[params.position]; 107 | if(existingElement) container.insertBefore(fieldWrapper , existingElement); 108 | }else { 109 | throw new Error(`Invalid position value: ${params.position}`); 110 | 111 | } 112 | 113 | this.register({ 114 | id: params.fieldId, 115 | initialValue: params.register?.initialValue || "", 116 | schemaValidation: params.register?.schemaValidation || [], 117 | customValidation: params.register?.customValidation || [] 118 | }); 119 | 120 | 121 | } 122 | 123 | /** 124 | * Get a field by its ID. 125 | * @param {string} id - The id of the form field. 126 | * @returns {Field|undefined} The field object if found, undefined otherwise. 127 | */ 128 | public getField(id: string): Field | undefined { 129 | return this.fields.find(field => field.id === id); 130 | } 131 | 132 | /** 133 | * Get all form fields. 134 | * @returns {Field[]} An array of all form fields. 135 | */ 136 | public getAllFields(): Field[] { 137 | return this.fields; 138 | } 139 | 140 | /** 141 | * Set the value of a form field. 142 | * @param {string} id - The id of the form field. 143 | * @param {string} value - The new value to set. 144 | */ 145 | public setValue(id: string, value: string) { 146 | const field = this.getField(id); 147 | if(!field) return; 148 | field.value = value; 149 | 150 | } 151 | 152 | /** 153 | * Get the value of a form field. 154 | * @param {string} id - The id of the form field. 155 | * @returns {string|undefined} The value of the field if found, undefined otherwise. 156 | */ 157 | public getValue(id: string): string | undefined { 158 | const field = this.getField(id); 159 | return field?.value; 160 | } 161 | 162 | /** 163 | * Get all form field values. 164 | * @returns {Object} An object with all form field ids as keys and their current values as values. 165 | */ 166 | public getValues(): { [key: string]: string } { 167 | const values: { [key: string]: string } = {}; 168 | this.fields.forEach(field => { 169 | values[field.id] = field.value; 170 | }); 171 | return values; 172 | } 173 | 174 | /** 175 | * Reset all form fields to their initial state. 176 | */ 177 | public resetFields() { 178 | this.fields.forEach(field => { 179 | field.value = ''; 180 | field.error = null; 181 | const inputElement = document.getElementById(field.id) as HTMLInputElement; 182 | if (inputElement) { 183 | inputElement.value = ''; 184 | } 185 | }); 186 | } 187 | 188 | 189 | } -------------------------------------------------------------------------------- /src/cors/FormHandler.ts: -------------------------------------------------------------------------------- 1 | import { FieldManager } from './FieldManager'; 2 | import { ValidatorManager } from './ValidatorManager'; 3 | import { ErrorManager } from './ErrorManager'; 4 | import { EventManager } from './EventManger'; 5 | import { Mode, Hooks, RegisterParams, ErrorStyle, addFieldParams } from '../interfaces/index'; 6 | import { HooksHandler } from './HooksHandler'; 7 | import * as validation from "../validators/ValidationRules"; 8 | import { lang } from './LanguageManager'; 9 | 10 | /** 11 | * FormHandler class: Manages form validation, registration, and submission. 12 | */ 13 | 14 | export class FormHandler { 15 | private fieldManager: FieldManager; 16 | private hooksHandler : HooksHandler; 17 | private validatorManager: ValidatorManager; 18 | private errorManager: ErrorManager; 19 | private eventManager: EventManager; 20 | private mode: Mode = Mode.Default; 21 | /** 22 | * Built-in validation that can be used directly. 23 | */ 24 | public validation = validation; 25 | /** 26 | * Built-in lang that can be used directly. 27 | */ 28 | public lang = lang; 29 | 30 | constructor() { 31 | this.hooksHandler = new HooksHandler(); 32 | this.fieldManager = new FieldManager(); 33 | this.errorManager = new ErrorManager(this.fieldManager); 34 | this.validatorManager = new ValidatorManager(this.fieldManager , this.errorManager , this.hooksHandler); 35 | this.eventManager = new EventManager(this.fieldManager, this.validatorManager , this.hooksHandler); 36 | } 37 | 38 | /** 39 | * Register a form field with initial value and validators. 40 | * @param {RegisterParams} params - The parameters for registering a field. 41 | */ 42 | public register(params: RegisterParams) { 43 | this.hooksHandler.triggerHook('beforeFieldRegister' , params.id , params) 44 | this.fieldManager.register(params); 45 | if (this.mode === Mode.Runtime) { 46 | this.eventManager.addRuntimeValidation(params.id); 47 | } 48 | this.hooksHandler.triggerHook("afterFieldRegister" , params.id , params) 49 | 50 | this.attachFocusBlurListeners(params.id) 51 | // // Set up validation for this field and its dependencies 52 | if(params.dependencies) { 53 | this.setupFieldValidation(params.id); 54 | 55 | } 56 | } 57 | 58 | /** 59 | * Set up validation for a field and its dependencies. 60 | * @param {string} fieldId - The ID of the field to set up validation for. 61 | */ 62 | private setupFieldValidation(fieldId: string) { 63 | const field = this.fieldManager.getField(fieldId); 64 | if (!field) return; 65 | 66 | const inputElement = document.getElementById(fieldId) as HTMLInputElement; 67 | if (inputElement) { 68 | inputElement.addEventListener('input', () => { 69 | this.validatorManager.validateField(fieldId); 70 | }); 71 | } 72 | 73 | // Set up validation for dependent fields 74 | if (field.dependencies) { 75 | field.dependencies.forEach(depId => { 76 | const depElement = document.getElementById(depId) as HTMLInputElement; 77 | if (depElement) { 78 | depElement.addEventListener('input', () => { 79 | this.validatorManager.validateField(fieldId); 80 | }); 81 | } 82 | }); 83 | } 84 | } 85 | 86 | /** 87 | * Set the mode for the form handler. 88 | * @param {Mode} newMode - The new mode to set. 89 | */ 90 | public setMode(newMode: Mode) { 91 | this.mode = newMode; 92 | } 93 | 94 | /** 95 | * Handle form submission. 96 | * @param {string} formId - The ID of the form element. 97 | * @param {Function} [onSubmit] - Optional custom submission handler. 98 | */ 99 | public submitHandler(formId: string, onSubmit?: (values: { [key: string]: string }) => void) { 100 | const form = document.getElementById(formId) as HTMLFormElement; 101 | if (!form) throw new Error("Form not found. Please ensure the form ID is correct!"); 102 | form.addEventListener('submit', async (e) => { 103 | e.preventDefault(); 104 | await this.validatorManager.validateAll(); 105 | const values = this.fieldManager.getValues(); 106 | const hasErrors = this.errorManager.hasErrors() 107 | if (hasErrors) { 108 | this.errorManager.displayErrors(this.fieldManager.getAllFields()); 109 | this.hooksHandler.triggerHook('onFormSubmitFail' , this.errorManager.getErrors()) 110 | } else if (onSubmit) { 111 | onSubmit(values); 112 | this.hooksHandler.triggerHook("onFormSubmitSuccess", values) 113 | 114 | } 115 | }); 116 | } 117 | 118 | 119 | 120 | /** 121 | * Get the current value of a form field. 122 | * @param {string} id - The id of the form field. 123 | * @returns {string|undefined} The value of the form field. 124 | */ 125 | public getValue(id: string): string | undefined { 126 | return this.fieldManager.getValue(id); 127 | } 128 | 129 | /** 130 | * Get all form field values. 131 | * @returns {Object} An object with all form field ids as keys and their current values as values. 132 | */ 133 | public getValues(): { [key: string]: string } { 134 | return this.fieldManager.getValues(); 135 | } 136 | 137 | /** 138 | * Reset the form fields and errors. 139 | */ 140 | public resetForm() { 141 | this.fieldManager.resetFields(); 142 | this.errorManager.clearErrors(); 143 | this.hooksHandler.triggerHook('onFormReset') 144 | } 145 | 146 | /** 147 | * Dynamically remove a form field. 148 | * @param {string} fieldId - The id of the form field. 149 | * @param {string} disabledId -The ID of a button element that should be disabled when the field is added. 150 | */ 151 | public removeField(params : {fieldId : string , disabledId:string}) { 152 | this.fieldManager.removeField(params); 153 | this.hooksHandler.triggerHook('onFieldRemove' , params.fieldId) 154 | } 155 | 156 | /** 157 | * Dynamically add a form field to a specific position in the container. 158 | * @param {Object} params - Parameters for adding a new field. 159 | * @param {string} params.containerId - The ID of the container element where the field should be added. 160 | * @param {string} params.fieldId - The ID of the new form field. 161 | * @param {string} params.labelText - The text to display as the label for the form field. 162 | * @param {RegisterParams} [params.register] - Optional parameters for registering the field. 163 | * @param {string|number} [params.position] - The position where the new field should be inserted. 164 | * @param {string} [params.disabled] - The ID of a button element that should be disabled when the field is added. 165 | */ 166 | public addField(params: addFieldParams) { 167 | this.fieldManager.addField(params); 168 | if (this.mode === Mode.Runtime) { 169 | this.eventManager.addRuntimeValidation(params.fieldId); 170 | } 171 | this.errorManager.displayErrors(this.fieldManager.getAllFields()); 172 | this.hooksHandler.triggerHook("onFieldAdd" , params.fieldId) 173 | 174 | } 175 | 176 | /** 177 | * Set error styles to be applied to error elements. 178 | * @param {ErrorStyle} style - An error style object to be applied. 179 | */ 180 | public setErrorStyles(style: ErrorStyle) { 181 | this.errorManager.setErrorStyles(style); 182 | } 183 | 184 | /** 185 | * Add hooks for the form handler. 186 | * @param {Hooks} hooks - Hooks to be set. 187 | */ 188 | public addHooks(hooks: Hooks) { 189 | this.hooksHandler.addHooks(hooks); 190 | } 191 | 192 | /** 193 | * Attach a custom event listener to a form field. 194 | * @param {string} id - The id of the form field. 195 | * @param {string} event - The event type (e.g., 'input', 'change'). 196 | * @param {EventListener} listener - The event listener function. 197 | */ 198 | public addCustomEventListener(id: string, event: string, listener: EventListener) { 199 | this.eventManager.addCustomEventListener(id, event, listener); 200 | } 201 | 202 | /** 203 | * Attaches focus and blur event listeners to an input field. 204 | * @param {id} id - The parameters required to register the field. 205 | */ 206 | public attachFocusBlurListeners(id: string): void { 207 | const inputElement = document.getElementById(id) as HTMLInputElement; 208 | 209 | if (inputElement) { 210 | // Attach focus event listener 211 | inputElement.addEventListener('focus', () => { 212 | const field = this.fieldManager.getField(id); 213 | if (field) { 214 | this.hooksHandler.triggerHook('onFocus', field); 215 | } 216 | }); 217 | 218 | // Attach blur event listener 219 | inputElement.addEventListener('blur', () => { 220 | const field = this.fieldManager.getField(id); 221 | if (field) { 222 | this.hooksHandler.triggerHook('onBlur', field); 223 | } 224 | }); 225 | } else { 226 | console.warn(`Input element with id "${id}" not found.`); 227 | } 228 | } 229 | 230 | 231 | } -------------------------------------------------------------------------------- /src/cors/HooksHandler.ts: -------------------------------------------------------------------------------- 1 | import { Hooks } from '../interfaces/index'; 2 | 3 | export class HooksHandler { 4 | private hooks: Hooks = {}; 5 | 6 | /** 7 | * Add hooks for form events. 8 | * @param {Hooks} hooks - Hooks to be added. 9 | */ 10 | public addHooks(hooks: Hooks) { 11 | this.hooks = hooks; 12 | } 13 | 14 | /** 15 | * Get all hooks. 16 | * @returns {Hooks} - The hooks object. 17 | */ 18 | public getHooks() { 19 | return this.hooks; 20 | } 21 | 22 | /** 23 | * Get a specific hook by name. 24 | * @param {keyof Hooks} hookName - The name of the hook to retrieve. 25 | * @returns {Function | undefined} - The hook function or undefined if not found. 26 | */ 27 | public getHook(hookName: keyof Hooks) { 28 | return this.hooks[hookName]; 29 | } 30 | 31 | /** 32 | * Trigger a specific hook. 33 | * @param {keyof Hooks} hookName - The name of the hook to trigger. 34 | * @param {...any} args - Arguments to pass to the hook. 35 | */ 36 | public triggerHook(hookName: keyof Hooks, ...args: any) { 37 | const hook = this.getHook(hookName); 38 | if (hook) { 39 | (hook as Function)(...args); 40 | } 41 | } 42 | 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/cors/LanguageManager.ts: -------------------------------------------------------------------------------- 1 | 2 | export class LanguageManager { 3 | private translations : any = {}; 4 | private currentLanguage: string = 'en'; 5 | 6 | /** 7 | * Set the current language. 8 | * @param {string} langCode - The language code to set. 9 | */ 10 | public setCurrentLanguage(langCode : string) { 11 | this.currentLanguage = langCode; 12 | } 13 | 14 | /** 15 | * get the current language. 16 | */ 17 | public getCurrentLanguage() : string { 18 | return this.currentLanguage; 19 | } 20 | 21 | /** 22 | * Add translations for a specific language. 23 | * @param {string} lang - The language code. 24 | * @param {Translations} translations - The translations to add. 25 | */ 26 | 27 | public addTranslations(lang: string, translations: { [key: string]: string; }): void { 28 | this.translations[lang] = { ...this.translations[lang], ...translations }; 29 | } 30 | 31 | /** 32 | * Get the translation for a key in the current language. 33 | * If the translation key is not found, the default message will be returned. 34 | * If a parameter is not found, the placeholder will be used as the default value and a warning will be logged. 35 | * @param {string} key - The key to translate. 36 | * @param {Object} [params] - Parameters to interpolate in the translated string. 37 | * @param {string} [defaultMessage] - The default message to use if the translation is not found. 38 | * @returns {string} The translated string, or the default message if the translation is not found. 39 | */ 40 | public getTranslation(key: string, params: { [key: string]: string | number } = {}, defaultMessage?: string): string { 41 | const translation = this.translations[this.currentLanguage]?.[key]; 42 | 43 | if (translation) { 44 | return translation.replace(/{(\w+)}/g, (match: string, paramKey: string) => { 45 | const paramValue = params[paramKey]; 46 | if (paramValue !== undefined) { 47 | return paramValue.toString(); 48 | } else { 49 | return match; 50 | } 51 | }); 52 | } else { 53 | return defaultMessage || key; 54 | } 55 | } 56 | } 57 | // Create and export a singleton instance 58 | export const lang = new LanguageManager(); -------------------------------------------------------------------------------- /src/cors/ValidatorManager.ts: -------------------------------------------------------------------------------- 1 | import { FieldManager } from './FieldManager'; 2 | import { Field } from '../interfaces/index'; 3 | import { ErrorManager } from './ErrorManager'; 4 | import { HooksHandler } from './HooksHandler'; 5 | 6 | /** 7 | * ValidatorManager class: Handles form field validation. 8 | */ 9 | export class ValidatorManager { 10 | constructor(private fieldManager: FieldManager , private errorManager : ErrorManager , private hooksHandler : HooksHandler ) {} 11 | 12 | /** 13 | * Validate all form fields. 14 | * @returns {Promise} 15 | */ 16 | public async validateAll() { 17 | const fields = this.fieldManager.getAllFields(); 18 | this.hooksHandler.triggerHook("onValidationStart" , fields) 19 | for (const field of fields) { 20 | await this.validateField(field.id); 21 | } 22 | this.hooksHandler.triggerHook("onValidationEnd" , fields) 23 | } 24 | 25 | 26 | /** 27 | * Validate a single form field. 28 | * @param {string} id - The id of the form field to validate. 29 | * @returns {Promise} 30 | */ 31 | public async validateField(id: string): Promise { 32 | const field = this.fieldManager.getField(id); 33 | if (!field) return; 34 | const formValues = this.fieldManager.getValues(); 35 | // First, check schema validation 36 | const schemaError = this.validateFieldSchema(field, formValues); 37 | if (schemaError) { 38 | this.updateFieldError(field, schemaError); 39 | this.hooksHandler.triggerHook('onFieldValidationError', id, schemaError); 40 | return; 41 | } 42 | // If no schema error, proceed with custom validation 43 | const customError = await this.validateCustomValidator(field, formValues); 44 | this.updateFieldError(field, customError); 45 | if (customError) { 46 | this.hooksHandler.triggerHook('onFieldValidationError', id, customError); 47 | } 48 | 49 | } 50 | 51 | 52 | /** 53 | * Validate a field against its schema validators. 54 | * @param {Field} field - The form field to validate. 55 | * @param {{ [key: string]: string }} formValues - The current values of all form fields. 56 | * @returns {string|null} The error message if validation fails, otherwise null. 57 | */ 58 | private validateFieldSchema(field: Field , formValues: { [key: string]: string }): string | null { 59 | if (field.schemaValidation) { 60 | for (const validator of field.schemaValidation) { 61 | const error = validator(field.value , formValues); 62 | if (error) return error; 63 | } 64 | } 65 | return null; 66 | } 67 | /** 68 | * Perform custom validations on a given form field. 69 | * @param {Field} field - The form field to validate. 70 | * @param {{ [key: string]: string }} formValues - The current values of all form fields. 71 | * @returns {Promise} The error message if validation fails, otherwise null. 72 | */ 73 | private async validateCustomValidator(field: Field, formValues: { [key: string]: string }): Promise { 74 | if (!field.customValidation) return null; 75 | 76 | for (const { validate, message } of field.customValidation) { 77 | try { 78 | const isValid = await Promise.race([ 79 | Promise.resolve(validate(field.value, formValues)), 80 | new Promise((_, reject) => setTimeout(() => reject(new Error('Validation timeout')), 5000)) 81 | ]); 82 | 83 | if (!isValid) { 84 | return message; 85 | } 86 | } catch (error) { 87 | console.error(`Validation error for field ${field.id}:`, error); 88 | return (error as any).message === 'Validation timeout' 89 | ? 'Validation took too long. Please try again.' 90 | : 'An unexpected error occurred during validation.'; 91 | } 92 | } 93 | 94 | return null; 95 | } 96 | 97 | 98 | /** 99 | * Sets up validation for a field that depends on another field's value. 100 | * @param {string} fieldId - The ID of the field that depends on others. 101 | * @param {string[]} dependencies - The IDs of the fields this field depends on. 102 | */ 103 | public setupDependencyListeners(fieldId: string, dependencies: string[]) { 104 | dependencies.forEach(depId => { 105 | const depElement = document.getElementById(depId) as HTMLInputElement; 106 | if (depElement) { 107 | depElement.addEventListener('input', () => { 108 | this.validateField(fieldId) 109 | }); 110 | } 111 | }); 112 | } 113 | 114 | /** 115 | * Updates the error message for a field and displays all errors. 116 | * @param {Field} field - The field to update. 117 | * @param {string | null} error - The error message to set, or null if there is no error. 118 | */ 119 | private updateFieldError(field: Field, error: string | null): void { 120 | field.error = error; 121 | this.errorManager.displayErrors(this.fieldManager.getAllFields()); 122 | } 123 | } 124 | 125 | 126 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { FormHandler } from './cors/FormHandler'; 2 | import { required, minLength, maxLength, pattern, email } from "./validators/ValidationRules"; 3 | 4 | // Make FormHandler and validators available globally in browser environments 5 | if (typeof window !== 'undefined') { 6 | (window as any).shadowFormHandler = { 7 | FormHandler, 8 | required, 9 | minLength, 10 | maxLength, 11 | pattern, 12 | email 13 | }; 14 | } 15 | 16 | export { FormHandler, required, minLength, maxLength, pattern, email }; -------------------------------------------------------------------------------- /src/interfaces/index.ts: -------------------------------------------------------------------------------- 1 | 2 | // SchemaValidator type: A function that takes a string value and returns a string error message or null if valid. 3 | export type SchemaValidator = (value: string , formValues : {[key : string ] : string}) => string | null; 4 | 5 | // Define an enum for the mode 6 | export const enum Mode { 7 | 8 | Default = "default", 9 | Runtime = "runtime" 10 | } 11 | 12 | // Validator interface: Represents a custom validator with a validate function and an error message. 13 | export interface CustomValidator { 14 | validate: (value: string , formValues : {[key : string] : string}) => boolean; 15 | message: string; 16 | } 17 | 18 | // Field interface: Represents a form field with its properties. 19 | export interface Field { 20 | id: string; 21 | value: string; 22 | error: string | null; 23 | customValidation?: Array; 24 | schemaValidation?: Array; 25 | dependencies?: string[]; // New property to store dependent field IDs 26 | 27 | } 28 | 29 | // Hooks interface: Represents hooks for various form validation stages. 30 | export interface Hooks { 31 | onFormSubmitSuccess?: (values: { [key: string]: string }) => void; 32 | onFormSubmitFail?: (errors: { [key: string]: string }) => void; 33 | onFieldValidationError?: (fieldId: string, error: string) => void; 34 | onFormReset?: () => void; 35 | beforeFieldRegister?: (fieldId: string, params: RegisterParams) => void; 36 | afterFieldRegister?: (fieldId: string, params: RegisterParams) => void; 37 | onFocus?: (field: Field) => void; 38 | onBlur?: (field: Field) => void; 39 | onValueChange?: (fieldId: string, value: string) => void; 40 | onValidationStart?: (fields: Field[]) => void; 41 | onValidationEnd?: (fields: Field[]) => void; 42 | onFieldAdd?: (fieldId: string) => void; 43 | onFieldRemove?: (fieldId: string) => void; 44 | } 45 | 46 | // RegisterParams interface: Parameters for registering a form field. 47 | export interface RegisterParams { 48 | id: string; 49 | initialValue?: string; 50 | schemaValidation?: Array; 51 | customValidation?: Array; 52 | dependencies?: string[]; // New property to store dependent field IDs 53 | 54 | } 55 | 56 | // ErrorStyle interface: Represents CSS styles for error elements. 57 | export interface ErrorStyle { 58 | property: string; 59 | value: string; 60 | } 61 | 62 | export interface addFieldParams { 63 | containerId: string, 64 | fieldId: string, 65 | labelText: string, 66 | register?: RegisterParams, 67 | position?: "top" | "bottom" | number, 68 | disabledId?: string 69 | } 70 | 71 | -------------------------------------------------------------------------------- /src/validators/ValidationRules.ts: -------------------------------------------------------------------------------- 1 | import { lang } from "../cors/LanguageManager"; 2 | 3 | /** 4 | * Validation rule to check if the value is required. 5 | * @param {string} [message] - The error message to display if validation fails. Defaults to the translation for 'required'. 6 | * @returns A function that validates if the value is non-empty. 7 | */ 8 | export const required = (message?: string) => 9 | (value: string) => value.trim() ? null : message || lang.getTranslation('required', {}, 'This field is required.'); 10 | 11 | /** 12 | * Validation rule to check if the value meets the minimum length requirement. 13 | * @param {number} min - The minimum length required. 14 | * @param {string} [message] - The error message to display if validation fails. Defaults to the translation for 'minLength'. 15 | * @returns A function that validates if the value has the minimum length. 16 | */ 17 | export const minLength = (min: number, message?: string) => 18 | (value: string) => value.length >= min ? null : message || lang.getTranslation('minLength', { min }, `This field must be at least ${min} characters long.`); 19 | 20 | /** 21 | * Validation rule to check if the value meets the maximum length requirement. 22 | * @param {number} max - The maximum length allowed. 23 | * @param {string} [message] - The error message to display if validation fails. Defaults to the translation for 'maxLength'. 24 | * @returns A function that validates if the value has the maximum length. 25 | */ 26 | export const maxLength = (max: number, message?: string) => 27 | (value: string) => value.length <= max ? null : message || lang.getTranslation('maxLength', { max }, `This field must be no more than ${max} characters long.`); 28 | 29 | /** 30 | * Validation rule to check if the value matches a regular expression pattern. 31 | * @param {RegExp} pattern - The regular expression pattern to match. 32 | * @param {string} [message] - The error message to display if validation fails. Defaults to the translation for 'pattern'. 33 | * @returns A function that validates if the value matches the pattern. 34 | */ 35 | export const pattern = (regex: RegExp, message?: string) => 36 | (value: string) => regex.test(value) ? null : message || lang.getTranslation('pattern', {}, 'This field does not match the required pattern.'); 37 | 38 | /** 39 | * Validation rule to check if the value is a valid email format. 40 | * @param {string} [message] - The error message to display if validation fails. Defaults to the translation for 'email'. 41 | * @returns A function that validates if the value is a valid email. 42 | */ 43 | export const email = (message?: string) => 44 | pattern(/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/, message || lang.getTranslation('email', {}, 'This field must be a valid email address.')); 45 | 46 | /** 47 | * Checks if the value is a number within a specified range. 48 | * @param {number} min - Minimum value (inclusive). 49 | * @param {number} max - Maximum value (inclusive). 50 | * @param {string} [message] - The error message to display if validation fails. Defaults to the translation for 'range'. 51 | */ 52 | export const range = (min: number, max: number, message?: string) => 53 | (value: string) => { 54 | const num = parseFloat(value); 55 | return !isNaN(num) && num >= min && num <= max ? null : message || lang.getTranslation('range', { min, max }, `The value must be between ${min} and ${max}.`); 56 | }; 57 | 58 | /** 59 | * Checks if the value matches another field's value. 60 | * @param {string} fieldToMatch - ID of the field to match against. 61 | * @param {string} [message] - The error message to display if validation fails. Defaults to the translation for 'matches'. 62 | */ 63 | export const matches = (fieldToMatch: string, message?: string) => 64 | (value: string, formValues?: { [key: string]: string }) => 65 | formValues && value === formValues[fieldToMatch] ? null : message || lang.getTranslation('matches', { fieldToMatch }, `This field must match the ${fieldToMatch} field.`); 66 | 67 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./dist", 4 | "module": "CommonJS", 5 | "target": "ES5", 6 | "sourceMap": true, 7 | "strict": true, 8 | "esModuleInterop": true, 9 | "skipLibCheck": true, 10 | "declaration": true, 11 | "moduleResolution": "node" 12 | }, 13 | "include": ["src"], 14 | "exclude": ["node_modules"] 15 | } 16 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | mode: 'development', 5 | entry: './src/index.ts', 6 | output: { 7 | filename: 'shadow-form-handler.js', 8 | path: path.resolve(__dirname, 'dist'), 9 | }, 10 | resolve: { 11 | extensions: ['.ts', '.js'], 12 | }, 13 | module: { 14 | rules: [ 15 | { 16 | test: /\.ts$/, 17 | use: 'ts-loader', 18 | exclude: /node_modules/, 19 | }, 20 | ], 21 | }, 22 | }; 23 | --------------------------------------------------------------------------------