UNPKG

angular-dynamic-forms-ai

Version:

AI-enhanced dynamic form generator for Angular using Template-Driven Forms

444 lines (421 loc) 25.8 kB
import * as i0 from '@angular/core'; import { Injectable, EventEmitter, Component, Input, Output, NgModule } from '@angular/core'; import * as i2 from '@angular/common'; import { CommonModule } from '@angular/common'; import * as i3 from '@angular/forms'; import { FormsModule } from '@angular/forms'; class AiValidationService { validationRules = new Map([ ['email', /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/], ['phone', /^\+?[\d\s-]{10,}$/], ['url', /^(https?:\/\/)?([\da-z.-]+)\.([a-z.]{2,6})([\/\w .-]*)*\/?$/], ['zipcode', /^\d{5}(-\d{4})?$/], ]); suggestValidations(field) { const fieldNameLower = field.name.toLowerCase(); const validations = { ...field.validations } || {}; // Email validation if (fieldNameLower.includes('email')) { validations.email = true; validations.required = true; field.type = 'email'; } // Password validation if (fieldNameLower.includes('password')) { validations.required = true; validations.minLength = 6; field.type = 'password'; validations.pattern = '^(?=.*[A-Za-z])(?=.*\\d)[A-Za-z\\d]{6,}$'; } // Phone validation if (fieldNameLower.includes('phone')) { validations.pattern = this.validationRules.get('phone')?.toString().slice(1, -1); validations.required = true; } // Name validation if (fieldNameLower.includes('name')) { validations.required = true; validations.minLength = 2; } // URL validation if (fieldNameLower.includes('url') || fieldNameLower.includes('website')) { validations.pattern = this.validationRules.get('url')?.toString().slice(1, -1); } // Zip code validation if (fieldNameLower.includes('zip')) { validations.pattern = this.validationRules.get('zipcode')?.toString().slice(1, -1); } field.validations = validations; field.errorMessages = this.generateErrorMessages(field); return field; } generateErrorMessages(field) { const messages = {}; const validations = field.validations || {}; if (validations.required) { messages.required = `${field.label} is required`; } if (validations.minLength) { messages.minlength = `${field.label} must be at least ${validations.minLength} characters`; } if (validations.maxLength) { messages.maxlength = `${field.label} cannot exceed ${validations.maxLength} characters`; } if (validations.email) { messages.email = `Please enter a valid email address`; } if (validations.pattern) { messages.pattern = this.getPatternErrorMessage(field.name); } return messages; } getPatternErrorMessage(fieldName) { const fieldNameLower = fieldName.toLowerCase(); if (fieldNameLower.includes('password')) { return 'Password must contain at least one letter and one number'; } if (fieldNameLower.includes('phone')) { return 'Please enter a valid phone number'; } if (fieldNameLower.includes('url') || fieldNameLower.includes('website')) { return 'Please enter a valid URL'; } if (fieldNameLower.includes('zip')) { return 'Please enter a valid ZIP code'; } return 'Please enter a valid value'; } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.0.6", ngImport: i0, type: AiValidationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.0.6", ngImport: i0, type: AiValidationService, providedIn: 'root' }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.0.6", ngImport: i0, type: AiValidationService, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }] }); class DynamicFormComponent { aiValidationService; formConfig; formSubmit = new EventEmitter(); constructor(aiValidationService) { this.aiValidationService = aiValidationService; } ngOnInit() { // Apply AI-based validation rules to each field this.formConfig.fields = this.formConfig.fields.map(field => this.aiValidationService.suggestValidations(field)); } isFieldVisible(field) { if (!field.conditionalDisplay) { return true; } const dependentField = this.formConfig.fields.find(f => f.name === field.conditionalDisplay?.dependsOn); return dependentField?.value === field.conditionalDisplay.showWhen; } onSubmit(form) { if (form.valid) { const formData = this.formConfig.fields.reduce((acc, field) => { if (this.isFieldVisible(field)) { acc[field.name] = field.value; } return acc; }, {}); this.formSubmit.emit(formData); } } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.0.6", ngImport: i0, type: DynamicFormComponent, deps: [{ token: AiValidationService }], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.0.6", type: DynamicFormComponent, selector: "lib-dynamic-form", inputs: { formConfig: "formConfig" }, outputs: { formSubmit: "formSubmit" }, ngImport: i0, template: ` <form #form="ngForm" (ngSubmit)="onSubmit(form)" class="dynamic-form" [attr.aria-label]="formConfig.fields[0]?.label + ' form'"> <div *ngFor="let field of formConfig.fields" class="form-field" [ngClass]="{'hidden': !isFieldVisible(field)}"> <label [for]="field.name" class="form-label"> {{ field.label }} <span *ngIf="field.validations?.required" class="required-marker">*</span> </label> <ng-container [ngSwitch]="field.type"> <!-- Text, Email, Number, Password inputs --> <input *ngSwitchCase="'text'" [type]="field.type" [id]="field.name" [name]="field.name" [placeholder]="field.placeholder || ''" [(ngModel)]="field.value" #fieldModel="ngModel" [required]="field.validations?.required" [minlength]="field.validations?.minLength" [maxlength]="field.validations?.maxLength" [pattern]="field.validations?.pattern" [email]="field.validations?.email" class="form-input" [ngClass]="{'error': fieldModel.invalid && (fieldModel.dirty || fieldModel.touched)}" [attr.aria-invalid]="fieldModel.invalid && (fieldModel.dirty || fieldModel.touched)" [attr.aria-describedby]="field.name + '-error'"> <input *ngSwitchCase="'email'" type="email" [id]="field.name" [name]="field.name" [placeholder]="field.placeholder || ''" [(ngModel)]="field.value" #fieldModel="ngModel" [required]="field.validations?.required" [email]="true" class="form-input" [ngClass]="{'error': fieldModel.invalid && (fieldModel.dirty || fieldModel.touched)}" [attr.aria-invalid]="fieldModel.invalid && (fieldModel.dirty || fieldModel.touched)" [attr.aria-describedby]="field.name + '-error'"> <input *ngSwitchCase="'number'" type="number" [id]="field.name" [name]="field.name" [placeholder]="field.placeholder || ''" [(ngModel)]="field.value" #fieldModel="ngModel" [required]="field.validations?.required" class="form-input" [ngClass]="{'error': fieldModel.invalid && (fieldModel.dirty || fieldModel.touched)}" [attr.aria-invalid]="fieldModel.invalid && (fieldModel.dirty || fieldModel.touched)" [attr.aria-describedby]="field.name + '-error'"> <input *ngSwitchCase="'password'" type="password" [id]="field.name" [name]="field.name" [placeholder]="field.placeholder || ''" [(ngModel)]="field.value" #fieldModel="ngModel" [required]="field.validations?.required" [minlength]="field.validations?.minLength" [pattern]="field.validations?.pattern" class="form-input" [ngClass]="{'error': fieldModel.invalid && (fieldModel.dirty || fieldModel.touched)}" [attr.aria-invalid]="fieldModel.invalid && (fieldModel.dirty || fieldModel.touched)" [attr.aria-describedby]="field.name + '-error'"> <!-- Select dropdown --> <select *ngSwitchCase="'select'" [id]="field.name" [name]="field.name" [(ngModel)]="field.value" #fieldModel="ngModel" [required]="field.validations?.required" class="form-select" [ngClass]="{'error': fieldModel.invalid && (fieldModel.dirty || fieldModel.touched)}" [attr.aria-invalid]="fieldModel.invalid && (fieldModel.dirty || fieldModel.touched)" [attr.aria-describedby]="field.name + '-error'"> <option value="">Select {{ field.label }}</option> <option *ngFor="let option of field.options" [value]="option.value"> {{ option.label }} </option> </select> <!-- Textarea --> <textarea *ngSwitchCase="'textarea'" [id]="field.name" [name]="field.name" [placeholder]="field.placeholder || ''" [(ngModel)]="field.value" #fieldModel="ngModel" [required]="field.validations?.required" [minlength]="field.validations?.minLength" [maxlength]="field.validations?.maxLength" class="form-textarea" [ngClass]="{'error': fieldModel.invalid && (fieldModel.dirty || fieldModel.touched)}" [attr.aria-invalid]="fieldModel.invalid && (fieldModel.dirty || fieldModel.touched)" [attr.aria-describedby]="field.name + '-error'"> </textarea> </ng-container> <!-- Error messages --> <div *ngIf="fieldModel.invalid && (fieldModel.dirty || fieldModel.touched)" class="error-messages" [id]="field.name + '-error'" role="alert"> <div *ngIf="fieldModel.errors?.required"> {{ field.errorMessages?.required }} </div> <div *ngIf="fieldModel.errors?.minlength"> {{ field.errorMessages?.minlength }} </div> <div *ngIf="fieldModel.errors?.maxlength"> {{ field.errorMessages?.maxlength }} </div> <div *ngIf="fieldModel.errors?.email"> {{ field.errorMessages?.email }} </div> <div *ngIf="fieldModel.errors?.pattern"> {{ field.errorMessages?.pattern }} </div> </div> </div> <button type="submit" [disabled]="!form.valid" [class]="formConfig.submitButton?.class || 'submit-button'" [attr.aria-disabled]="!form.valid"> {{ formConfig.submitButton?.text || 'Submit' }} </button> </form> `, isInline: true, styles: [".dynamic-form{max-width:600px;margin:0 auto;padding:20px;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif}.form-field{margin-bottom:20px}.form-label{display:block;margin-bottom:8px;font-weight:500;color:#333}.required-marker{color:#dc3545;margin-left:4px}.form-input,.form-select,.form-textarea{width:100%;padding:8px 12px;border:1px solid #ced4da;border-radius:4px;font-size:16px;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}.form-textarea{min-height:100px;resize:vertical}.form-input:focus,.form-select:focus,.form-textarea:focus{border-color:#80bdff;outline:0;box-shadow:0 0 0 .2rem #007bff40}.error{border-color:#dc3545}.error:focus{border-color:#dc3545;box-shadow:0 0 0 .2rem #dc354540}.error-messages{margin-top:5px;color:#dc3545;font-size:14px}.hidden{display:none}.submit-button{background-color:#007bff;color:#fff;padding:10px 20px;border:none;border-radius:4px;font-size:16px;cursor:pointer;transition:background-color .15s ease-in-out}.submit-button:hover:not(:disabled){background-color:#0056b3}.submit-button:disabled{background-color:#6c757d;cursor:not-allowed;opacity:.65}@media (max-width: 768px){.dynamic-form{padding:15px}}\n"], dependencies: [{ kind: "directive", type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2.NgSwitch, selector: "[ngSwitch]", inputs: ["ngSwitch"] }, { kind: "directive", type: i2.NgSwitchCase, selector: "[ngSwitchCase]", inputs: ["ngSwitchCase"] }, { kind: "directive", type: i3.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i3.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i3.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i3.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i3.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i3.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i3.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i3.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i3.MinLengthValidator, selector: "[minlength][formControlName],[minlength][formControl],[minlength][ngModel]", inputs: ["minlength"] }, { kind: "directive", type: i3.MaxLengthValidator, selector: "[maxlength][formControlName],[maxlength][formControl],[maxlength][ngModel]", inputs: ["maxlength"] }, { kind: "directive", type: i3.PatternValidator, selector: "[pattern][formControlName],[pattern][formControl],[pattern][ngModel]", inputs: ["pattern"] }, { kind: "directive", type: i3.EmailValidator, selector: "[email][formControlName],[email][formControl],[email][ngModel]", inputs: ["email"] }, { kind: "directive", type: i3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i3.NgForm, selector: "form:not([ngNoForm]):not([formGroup]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }] }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.0.6", ngImport: i0, type: DynamicFormComponent, decorators: [{ type: Component, args: [{ selector: 'lib-dynamic-form', template: ` <form #form="ngForm" (ngSubmit)="onSubmit(form)" class="dynamic-form" [attr.aria-label]="formConfig.fields[0]?.label + ' form'"> <div *ngFor="let field of formConfig.fields" class="form-field" [ngClass]="{'hidden': !isFieldVisible(field)}"> <label [for]="field.name" class="form-label"> {{ field.label }} <span *ngIf="field.validations?.required" class="required-marker">*</span> </label> <ng-container [ngSwitch]="field.type"> <!-- Text, Email, Number, Password inputs --> <input *ngSwitchCase="'text'" [type]="field.type" [id]="field.name" [name]="field.name" [placeholder]="field.placeholder || ''" [(ngModel)]="field.value" #fieldModel="ngModel" [required]="field.validations?.required" [minlength]="field.validations?.minLength" [maxlength]="field.validations?.maxLength" [pattern]="field.validations?.pattern" [email]="field.validations?.email" class="form-input" [ngClass]="{'error': fieldModel.invalid && (fieldModel.dirty || fieldModel.touched)}" [attr.aria-invalid]="fieldModel.invalid && (fieldModel.dirty || fieldModel.touched)" [attr.aria-describedby]="field.name + '-error'"> <input *ngSwitchCase="'email'" type="email" [id]="field.name" [name]="field.name" [placeholder]="field.placeholder || ''" [(ngModel)]="field.value" #fieldModel="ngModel" [required]="field.validations?.required" [email]="true" class="form-input" [ngClass]="{'error': fieldModel.invalid && (fieldModel.dirty || fieldModel.touched)}" [attr.aria-invalid]="fieldModel.invalid && (fieldModel.dirty || fieldModel.touched)" [attr.aria-describedby]="field.name + '-error'"> <input *ngSwitchCase="'number'" type="number" [id]="field.name" [name]="field.name" [placeholder]="field.placeholder || ''" [(ngModel)]="field.value" #fieldModel="ngModel" [required]="field.validations?.required" class="form-input" [ngClass]="{'error': fieldModel.invalid && (fieldModel.dirty || fieldModel.touched)}" [attr.aria-invalid]="fieldModel.invalid && (fieldModel.dirty || fieldModel.touched)" [attr.aria-describedby]="field.name + '-error'"> <input *ngSwitchCase="'password'" type="password" [id]="field.name" [name]="field.name" [placeholder]="field.placeholder || ''" [(ngModel)]="field.value" #fieldModel="ngModel" [required]="field.validations?.required" [minlength]="field.validations?.minLength" [pattern]="field.validations?.pattern" class="form-input" [ngClass]="{'error': fieldModel.invalid && (fieldModel.dirty || fieldModel.touched)}" [attr.aria-invalid]="fieldModel.invalid && (fieldModel.dirty || fieldModel.touched)" [attr.aria-describedby]="field.name + '-error'"> <!-- Select dropdown --> <select *ngSwitchCase="'select'" [id]="field.name" [name]="field.name" [(ngModel)]="field.value" #fieldModel="ngModel" [required]="field.validations?.required" class="form-select" [ngClass]="{'error': fieldModel.invalid && (fieldModel.dirty || fieldModel.touched)}" [attr.aria-invalid]="fieldModel.invalid && (fieldModel.dirty || fieldModel.touched)" [attr.aria-describedby]="field.name + '-error'"> <option value="">Select {{ field.label }}</option> <option *ngFor="let option of field.options" [value]="option.value"> {{ option.label }} </option> </select> <!-- Textarea --> <textarea *ngSwitchCase="'textarea'" [id]="field.name" [name]="field.name" [placeholder]="field.placeholder || ''" [(ngModel)]="field.value" #fieldModel="ngModel" [required]="field.validations?.required" [minlength]="field.validations?.minLength" [maxlength]="field.validations?.maxLength" class="form-textarea" [ngClass]="{'error': fieldModel.invalid && (fieldModel.dirty || fieldModel.touched)}" [attr.aria-invalid]="fieldModel.invalid && (fieldModel.dirty || fieldModel.touched)" [attr.aria-describedby]="field.name + '-error'"> </textarea> </ng-container> <!-- Error messages --> <div *ngIf="fieldModel.invalid && (fieldModel.dirty || fieldModel.touched)" class="error-messages" [id]="field.name + '-error'" role="alert"> <div *ngIf="fieldModel.errors?.required"> {{ field.errorMessages?.required }} </div> <div *ngIf="fieldModel.errors?.minlength"> {{ field.errorMessages?.minlength }} </div> <div *ngIf="fieldModel.errors?.maxlength"> {{ field.errorMessages?.maxlength }} </div> <div *ngIf="fieldModel.errors?.email"> {{ field.errorMessages?.email }} </div> <div *ngIf="fieldModel.errors?.pattern"> {{ field.errorMessages?.pattern }} </div> </div> </div> <button type="submit" [disabled]="!form.valid" [class]="formConfig.submitButton?.class || 'submit-button'" [attr.aria-disabled]="!form.valid"> {{ formConfig.submitButton?.text || 'Submit' }} </button> </form> `, styles: [".dynamic-form{max-width:600px;margin:0 auto;padding:20px;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif}.form-field{margin-bottom:20px}.form-label{display:block;margin-bottom:8px;font-weight:500;color:#333}.required-marker{color:#dc3545;margin-left:4px}.form-input,.form-select,.form-textarea{width:100%;padding:8px 12px;border:1px solid #ced4da;border-radius:4px;font-size:16px;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}.form-textarea{min-height:100px;resize:vertical}.form-input:focus,.form-select:focus,.form-textarea:focus{border-color:#80bdff;outline:0;box-shadow:0 0 0 .2rem #007bff40}.error{border-color:#dc3545}.error:focus{border-color:#dc3545;box-shadow:0 0 0 .2rem #dc354540}.error-messages{margin-top:5px;color:#dc3545;font-size:14px}.hidden{display:none}.submit-button{background-color:#007bff;color:#fff;padding:10px 20px;border:none;border-radius:4px;font-size:16px;cursor:pointer;transition:background-color .15s ease-in-out}.submit-button:hover:not(:disabled){background-color:#0056b3}.submit-button:disabled{background-color:#6c757d;cursor:not-allowed;opacity:.65}@media (max-width: 768px){.dynamic-form{padding:15px}}\n"] }] }], ctorParameters: function () { return [{ type: AiValidationService }]; }, propDecorators: { formConfig: [{ type: Input }], formSubmit: [{ type: Output }] } }); class DynamicFormsModule { static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.0.6", ngImport: i0, type: DynamicFormsModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "16.0.6", ngImport: i0, type: DynamicFormsModule, declarations: [DynamicFormComponent], imports: [CommonModule, FormsModule], exports: [DynamicFormComponent] }); static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "16.0.6", ngImport: i0, type: DynamicFormsModule, providers: [ AiValidationService ], imports: [CommonModule, FormsModule] }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.0.6", ngImport: i0, type: DynamicFormsModule, decorators: [{ type: NgModule, args: [{ declarations: [ DynamicFormComponent ], imports: [ CommonModule, FormsModule ], exports: [ DynamicFormComponent ], providers: [ AiValidationService ] }] }] }); /* * Public API Surface of dynamic-forms */ /** * Generated bundle index. Do not edit. */ export { AiValidationService, DynamicFormComponent, DynamicFormsModule }; //# sourceMappingURL=angular-dynamic-forms-ai.mjs.map