UNPKG

@nestledjs/forms

Version:

A flexible React form library supporting both declarative and imperative usage patterns with TypeScript support

793 lines (677 loc) β€’ 20.7 kB
# Forms Library Enhancement Roadmap ## Path to Building the Most Powerful Universal Form Library This document analyzes the current state of our forms library and outlines a roadmap for evolving it into the most powerful universal form library available. pnpm ## Current State Assessment 🎯 ### What We're Doing Exceptionally Well #### 1. **Type Safety Architecture** Our discriminated union system with `FormField` types provides perfect IntelliSense and prevents runtime errors. This is better than most form libraries. ```typescript // Our excellent type safety approach FormFieldProps<Extract<FormField, { type: FormFieldType.YourNewField }>> ``` #### 2. **DRY Component Architecture** The refactored search select base component pattern eliminates massive code duplication that plagues most libraries: - 80%+ code reduction in search select variants - Single source of truth for common patterns - Consistent behavior across all variants #### 3. **Theme System** The inheritance system where global styles flow down to field-specific themes is sophisticated: - Zod schema validation for themes - Type-safe theme merging - Consistent styling patterns - Easy customization without breaking changes #### 4. **Developer Experience** - Factory pattern with `FormFieldClass` is intuitive - Comprehensive documentation and creation guide - Both declarative and imperative APIs - Excellent Storybook integration - Clear separation of concerns #### 5. **Read-Only Patterns** The dual read-only modes (`'value'` vs `'disabled'`) show deep understanding of real-world needs. ## Enhancement Opportunities πŸš€ ### 1. **Advanced Validation System** **Current State:** ```typescript validate?: (value: any) => string | boolean ``` **Enhanced Vision:** ```typescript validation?: { schema?: ZodSchema | YupSchema | JoiSchema rules?: ValidationRule[] crossField?: (formValues: any) => ValidationResult async?: AsyncValidator debounce?: number showOn?: 'blur' | 'change' | 'submit' dependencies?: string[] // Other fields that affect this validation } // Example usage FormFieldClass.email('email', { label: 'Email', validation: { schema: z.string().email(), async: { validator: checkEmailAvailability, debounce: 500, message: 'Checking availability...' }, crossField: (values) => values.confirmEmail === values.email || 'Emails must match' } }) ``` ### 2. **Conditional Field Logic** βœ… **IMPLEMENTED** **COMPLETED:** Dynamic field behavior based on form values. **Implementation Details:** - Added `showWhen`, `requiredWhen`, and `disabledWhen` functions to `BaseFieldOptions` - Implemented in `RenderFormField` component for universal compatibility - Works with both declarative and imperative APIs - Performance optimized with `useMemo` and reactive form watching - Robust error handling for conditional functions - Dynamic validation updates when conditions change **Usage:** ```typescript FormFieldClass.text('email', { label: 'Email Address', showWhen: (formValues) => formValues.contactMethod === 'email', requiredWhen: (formValues) => formValues.accountType === 'business', disabledWhen: (formValues) => formValues.useCompanyEmail === true, }) // Complex nested conditions FormFieldClass.text('officeNumber', { label: 'Office Number', showWhen: (values) => values.userType === 'teacher' && values.isHeadOfDepartment, requiredWhen: (values) => values.userType === 'teacher' && values.isHeadOfDepartment, }) ``` **Benefits Achieved:** - βœ… Works with ALL existing field types immediately - βœ… No breaking changes to existing API - βœ… Consistent behavior across declarative/imperative usage - βœ… Dynamic validation that updates in real-time - βœ… Performance optimized with minimal re-renders - βœ… Comprehensive Storybook documentation and examples **Future Enhancement Ideas:** ```typescript // Advanced declarative conditions (potential future feature) conditions: { show: [ { field: 'contactMethod', operator: 'equals', value: 'email' }, { field: 'hasEmail', operator: 'equals', value: true } ], required: [ { field: 'accountType', operator: 'in', value: ['business', 'enterprise'] } ] } // Multi-step form support (potential future feature) FormFieldClass.group('personalInfo', { label: 'Personal Information', showWhen: (formValues, step) => step === 1, fields: [ FormFieldClass.text('firstName', { label: 'First Name' }), FormFieldClass.text('lastName', { label: 'Last Name' }) ] }) ``` ### 3. **Advanced Layout System** ```typescript FormFieldClass.text('firstName', { label: 'First Name', layout: { // Responsive grid system grid: { xs: 12, // Full width on mobile md: 6, // Half width on tablet lg: 4 // Third width on desktop }, // Flexbox properties flex: { grow: 1, shrink: 0, basis: 'auto' }, // CSS Grid properties gridArea: 'firstName', alignSelf: 'center' } }) // Advanced layout containers <Form id="complex-form" layout={{ type: 'grid', columns: { xs: 1, md: 2, lg: 3 }, gap: '1rem', areas: [ ['firstName', 'lastName', 'email'], ['address', 'address', 'phone'], ['notes', 'notes', 'submit'] ] }} fields={formFields} submit={handleSubmit} /> ``` ### 4. **State Management Enhancements** ```typescript <Form id="user-form" // Auto-save functionality autoSave={{ interval: 30000, // 30 seconds endpoint: '/api/drafts', onSave: (data) => console.log('Draft saved', data), onError: (error) => console.error('Save failed', error) }} // Persistence across browser sessions persistence={{ key: 'user-draft-123', storage: 'localStorage', // or 'sessionStorage' or 'indexedDB' encrypt: true, // For sensitive data expiry: 24 * 60 * 60 * 1000 // 24 hours }} // Undo/Redo functionality undoRedo={{ enabled: true, maxHistorySize: 50, ignoreFields: ['timestamp'], // Fields to exclude from history debounce: 1000 // Group rapid changes }} // Optimistic updates for real-time sync optimisticUpdates={{ enabled: true, syncEndpoint: '/api/forms/sync', conflictResolution: 'last-writer-wins' // or 'merge' or 'prompt-user' }} /> ``` ### 5. **Performance Optimizations** ```typescript // Lazy loading for heavy components const LazyRichTextEditor = lazy(() => import('./rich-text-field')) const LazyFileUploader = lazy(() => import('./file-upload-field')) // Virtual scrolling for massive forms <Form id="large-form" virtualScroll={{ enabled: true, windowSize: 20, bufferSize: 5, estimatedItemHeight: 80 }} fields={thousandsOfFields} /> // Field-level lazy loading FormFieldClass.richText('description', { label: 'Description', lazy: { threshold: 'visible', // Load when scrolled into view placeholder: <Skeleton height={100} /> } }) // Memoization and optimization FormFieldClass.select('country', { label: 'Country', options: countries, optimize: { memoize: true, virtualize: true, // For large option lists searchable: true, loadMore: true // Infinite scrolling for options } }) // Optimized conditional logic watching (future enhancement) FormFieldClass.text('email', { label: 'Email Address', showWhen: (values) => values.contactMethod === 'email', // Optional performance hint - watch only specific fields instead of entire form watchFields: ['contactMethod'], // Avoids unnecessary re-renders // Complex dependencies still work with full form watching requiredWhen: (values) => values.accountType === 'business' && values.hasEmail, // Auto-detected or falls back to watching all fields for complex logic }) // Advanced conditional performance options <Form id="optimized-form" conditionalOptimization={{ // Global strategy for conditional field watching strategy: 'targeted', // 'targeted' | 'full' | 'auto' // Automatic dependency detection (advanced feature) autoDependencies: true, // Debounce conditional evaluations for complex forms debounce: 100, // Performance monitoring monitor: process.env.NODE_ENV === 'development' }} fields={complexFormFields} /> ``` ### 6. **Advanced Field Types** Missing field types that would make it truly universal: ```typescript // Rich text editor with advanced features FormFieldClass.richText('content', { label: 'Content', features: ['bold', 'italic', 'links', 'mentions', 'images'], mentions: { trigger: '@', data: users, displayKey: 'name' }, imageUpload: { endpoint: '/api/upload', maxSize: '5MB', allowedTypes: ['jpg', 'png', 'gif'] } }) // File upload with drag/drop and progress FormFieldClass.fileUpload('documents', { label: 'Upload Documents', multiple: true, dragDrop: true, accept: '.pdf,.doc,.docx', maxSize: '10MB', upload: { endpoint: '/api/upload', chunkSize: 1024 * 1024, // 1MB chunks resumable: true, onProgress: (progress) => console.log(progress) }, preview: { enabled: true, thumbnails: true } }) // Date range picker FormFieldClass.dateRange('eventDates', { label: 'Event Duration', format: 'MM/DD/YYYY', presets: ['Today', 'This Week', 'This Month'], minDate: new Date(), maxDate: addYears(new Date(), 2), disabledDates: holidays }) // Color picker FormFieldClass.colorPicker('brandColor', { label: 'Brand Color', format: 'hex', // or 'rgb', 'hsl' presets: brandColors, alpha: true }) // Signature pad FormFieldClass.signature('signature', { label: 'Digital Signature', width: 400, height: 200, penColor: '#000', backgroundColor: '#fff', required: true }) // Location picker with maps FormFieldClass.location('address', { label: 'Address', provider: 'google', // or 'mapbox', 'osm' apiKey: process.env.MAPS_API_KEY, features: { autocomplete: true, geolocation: true, reverseGeocode: true }, restrictions: { country: 'US', bounds: boundingBox } }) // Barcode/QR scanner FormFieldClass.barcodeScanner('productCode', { label: 'Scan Product', formats: ['QR_CODE', 'CODE_128'], camera: { facingMode: 'environment' // Back camera }, onScan: (result) => console.log('Scanned:', result) }) ``` ### 7. **Real-Time Collaboration** ```typescript <Form id="shared-form" collaboration={{ enabled: true, websocketUrl: 'wss://api.example.com/collaborate', // Show other users' cursors and selections cursors: { enabled: true, showNames: true, colors: 'auto' // or custom color mapping }, // Show who's currently editing presence: { enabled: true, indicator: 'avatar', // or 'dot' or 'badge' position: 'field' // or 'form-header' }, // Handle simultaneous edits conflicts: { strategy: 'last-writer-wins', // or 'merge' or 'prompt' onConflict: (field, values) => resolveConflict(field, values) }, // Real-time sync sync: { debounce: 500, batchSize: 10, retryAttempts: 3 } }} onCollaboratorJoin={(user) => showNotification(`${user.name} joined`)} onCollaboratorLeave={(user) => showNotification(`${user.name} left`)} /> ``` ### 8. **Accessibility Excellence** ```typescript FormFieldClass.text('firstName', { label: 'First Name', accessibility: { // Enhanced ARIA support ariaLabel: 'Enter your first name', ariaDescribedBy: ['firstName-help', 'firstName-error'], // Screen reader announcements announcements: { onError: 'First name field has an error', onSuccess: 'First name is valid', onChange: true // Announce changes for screen readers }, // Keyboard navigation keyboardShortcut: 'Alt+F', // High contrast support highContrast: { enabled: true, customColors: { border: '#000', background: '#fff', text: '#000' } } } }) // Form-level accessibility features <Form id="accessible-form" accessibility={{ // Skip navigation skipNavigation: true, // Field grouping for screen readers fieldGroups: [ { name: 'personal', label: 'Personal Information', fields: ['firstName', 'lastName'] }, { name: 'contact', label: 'Contact Information', fields: ['email', 'phone'] } ], // Progress indication progressAnnouncement: true, // Error summary errorSummary: { enabled: true, position: 'top', focusOnError: true } }} /> ``` ### 9. **Internationalization** ```typescript <Form id="multilingual-form" i18n={{ locale: 'es-ES', messages: { required: 'Este campo es obligatorio', invalid: 'Por favor, introduce un valor vΓ‘lido', submit: 'Enviar' }, // Date and number formatting formatting: { date: 'DD/MM/YYYY', time: '24h', currency: 'EUR', decimal: ',', thousands: '.' }, // RTL support direction: 'rtl', // Dynamic loading loadMessages: (locale) => import(`./messages/${locale}.json`), // Fallback locale fallback: 'en-US' }} /> // Field-level internationalization FormFieldClass.text('name', { label: { en: 'Full Name', es: 'Nombre Completo', fr: 'Nom Complet' }, placeholder: { en: 'Enter your full name', es: 'Introduce tu nombre completo', fr: 'Entrez votre nom complet' } }) ``` ### 10. **Visual Form Builder** ```typescript // React component for visual form building <FormBuilder onFormChange={(formDefinition) => setForm(formDefinition)} // Available field types availableFields={[ 'text', 'email', 'password', 'number', 'select', 'multiSelect', 'checkbox', 'radio', 'date', 'file', 'richText', 'signature', 'location' ]} // Theme customization themes={[tailwindTheme, materialTheme, customTheme]} // Layout options layouts={['vertical', 'horizontal', 'grid', 'tabs', 'accordion']} // Advanced features features={{ conditionalLogic: true, validation: true, multiStep: true, collaboration: true, preview: true, export: ['json', 'typescript', 'react'] }} // Templates and presets templates={[ 'contact-form', 'registration-form', 'survey-form', 'application-form' ]} /> // Generated form definition const generatedForm = { id: 'contact-form', fields: [ FormFieldClass.text('name', { label: 'Name', required: true }), FormFieldClass.email('email', { label: 'Email', required: true }), FormFieldClass.textArea('message', { label: 'Message', rows: 4 }) ], layout: 'vertical', theme: 'tailwind', submit: async (values) => await submitForm(values) } ``` ## Architectural Enhancements ### 1. **Plugin System** ```typescript // Core plugin interface interface FormPlugin { name: string version: string install: (form: FormInstance) => void uninstall?: (form: FormInstance) => void } // Usage Form.use(ValidationPlugin) Form.use(PersistencePlugin) Form.use(I18nPlugin) Form.use(AnalyticsPlugin) // Custom plugin example const CustomAnalyticsPlugin: FormPlugin = { name: 'analytics', version: '1.0.0', install: (form) => { form.on('fieldChange', trackFieldChange) form.on('submit', trackSubmission) form.on('error', trackError) } } ``` ### 2. **Field Composition** ```typescript // Compose complex fields from simpler ones const AddressField = FormFieldClass.compose('address', { label: 'Address', layout: { type: 'grid', columns: { xs: 1, md: 2 }, gap: '1rem' }, fields: [ FormFieldClass.text('street', { label: 'Street Address', layout: { grid: { xs: 1, md: 2 } } }), FormFieldClass.text('city', { label: 'City' }), FormFieldClass.select('state', { label: 'State', options: states }), FormFieldClass.text('zipCode', { label: 'ZIP Code', validation: { pattern: /^\d{5}(-\d{4})?$/ } }) ], // Custom validation for the composed field validation: { crossField: (address) => { if (address.state === 'CA' && !address.zipCode.startsWith('9')) { return 'California ZIP codes should start with 9' } return true } }, // Data transformation transform: { output: (address) => ({ fullAddress: `${address.street}, ${address.city}, ${address.state} ${address.zipCode}`, ...address }) } }) // Usage FormFieldClass.address('billingAddress', { label: 'Billing Address', required: true }) ``` ### 3. **Enhanced Event System** ```typescript <Form id="event-driven-form" // Field-level events onFieldChange={(field, value, formValues, meta) => { console.log(`Field ${field} changed to ${value}`) console.log('Form values:', formValues) console.log('Metadata:', meta) // { source: 'user'|'programmatic', timestamp, etc. } }} onFieldFocus={(field, formValues) => { trackEvent('field_focus', { field }) }} onFieldBlur={(field, value, formValues) => { if (field === 'email') { validateEmailAsync(value) } }} // Form-level events onValidation={(errors, formValues, changedFields) => { console.log('Validation completed:', errors) updateErrorSummary(errors) }} onSubmitAttempt={(formValues, errors, isValid) => { if (!isValid) { trackEvent('form_submit_failed', { errors }) } }} onSubmitSuccess={(response, formValues) => { showSuccessMessage('Form submitted successfully!') trackEvent('form_submit_success') }} onSubmitError={(error, formValues) => { showErrorMessage('Submission failed. Please try again.') trackEvent('form_submit_error', { error }) }} // State change events onStateChange={(state, previousState) => { console.log('Form state changed:', { from: previousState, to: state }) }} // Custom events onCustomEvent={(eventName, data) => { handleCustomEvent(eventName, data) }} /> ``` ## Competitive Analysis ### Current Position vs Major Libraries **vs Formik:** - βœ… Better TypeScript support - βœ… Better performance (no unnecessary re-renders) - βœ… Built-in theme system - βœ… More consistent API **vs React Hook Form + UI Libraries:** - βœ… Better integration - βœ… Consistent theming across all fields - βœ… Type-safe field definitions - βœ… Less boilerplate **vs Ant Design Forms:** - βœ… More flexible and customizable - βœ… Better type safety - βœ… Framework agnostic theming - βœ… Cleaner architecture **vs Material-UI Forms:** - βœ… More extensible - βœ… Better performance - βœ… Framework agnostic - βœ… More comprehensive field types ## Implementation Priority Roadmap ### Phase 1: Foundation (1-2 months) 1. **Conditional field logic** - βœ… **COMPLETED** - Dynamic show/hide, required, and disabled logic 2. **Advanced validation system** - Schema integration + cross-field validation 3. **Enhanced event system** - Better developer experience 4. **Performance optimizations** - Memoization, lazy loading ### Phase 2: Advanced Features (2-3 months) 1. **Layout system** - Responsive grid/flex layouts 2. **State management** - Auto-save, persistence, undo/redo 3. **New field types** - Rich text, file upload, date ranges, location 4. **Accessibility enhancements** - WCAG 2.1 AA compliance ### Phase 3: Ecosystem (3-4 months) 1. **Plugin system** - Extensibility for third-party integrations 2. **Visual form builder** - No-code form creation 3. **Real-time collaboration** - Multi-user editing 4. **Internationalization** - Full i18n support ### Phase 4: Platform (4-6 months) 1. **Advanced field types** - Signature, barcode, drawing 2. **AI integrations** - Smart field suggestions, auto-completion 3. **Analytics and insights** - Form performance metrics 4. **Enterprise features** - SSO, compliance, audit trails ## Conclusion You've built an exceptional foundation with: - World-class type safety - DRY architecture that eliminates common problems - Sophisticated theme system - Excellent developer experience The enhancements outlined above would transform this from an excellent form library into the definitive form solution that could dominate the market. The architecture you've established makes implementing these features straightforward and maintainable. **You're not just "doing pretty good" - you've created something that could genuinely become the gold standard for form libraries.** The key is the solid foundation you've built. Every enhancement can be added incrementally without breaking existing APIs or patterns. This is the hallmark of truly great architecture.