UNPKG

@rxxuzi/gumi

Version:

Clean & minimal design system with delightful interactions

268 lines 9.15 kB
// components/form.ts // Gumi.js v1.0.0 - Form Validation import { $, on, addClass, removeClass } from '../core/dom'; import { createElement } from '../core/dom'; export class FormValidator { constructor(form, options = {}) { this.errors = new Map(); const el = $(form); if (!el || !(el instanceof HTMLFormElement)) { throw new Error('Form element not found'); } this.form = el; this.options = { showErrors: true, validateOnChange: true, ...options }; this.init(); } /** * Initialize form validator */ init() { // Prevent default HTML5 validation this.form.noValidate = true; // Add submit handler on(this.form, 'submit', (e) => { e.preventDefault(); if (this.validate()) { // Form is valid, trigger custom event const submitEvent = new CustomEvent('valid-submit', { detail: { form: this.form }, cancelable: true }); if (this.form.dispatchEvent(submitEvent)) { // Only submit if event wasn't prevented if (!this.options.rules) { this.form.submit(); } } } }); // Add change/input handlers if enabled if (this.options.validateOnChange) { const inputs = this.getInputs(); inputs.forEach(input => { on(input, 'blur', () => this.validateField(input)); on(input, 'input', () => { if (this.errors.has(input.name)) { this.validateField(input); } }); }); } } /** * Get all form inputs */ getInputs() { return Array.from(this.form.querySelectorAll('input, textarea, select')); } /** * Validate entire form */ validate() { this.clearErrors(); const inputs = this.getInputs(); let isValid = true; inputs.forEach(input => { if (!this.validateField(input)) { isValid = false; } }); return isValid; } /** * Validate single field */ validateField(input) { const name = input.name; const value = input.value.trim(); const type = input.type; // Clear previous error this.clearFieldError(input); // Check HTML5 validation attributes if (input.hasAttribute('required') && !value) { this.setFieldError(input, 'This field is required'); return false; } if (input.hasAttribute('minlength')) { const minLength = parseInt(input.getAttribute('minlength') || '0'); if (value.length < minLength) { this.setFieldError(input, `Minimum length is ${minLength} characters`); return false; } } if (input.hasAttribute('maxlength')) { const maxLength = parseInt(input.getAttribute('maxlength') || '0'); if (value.length > maxLength) { this.setFieldError(input, `Maximum length is ${maxLength} characters`); return false; } } if (input.hasAttribute('min') && type === 'number') { const min = parseFloat(input.getAttribute('min') || '0'); if (parseFloat(value) < min) { this.setFieldError(input, `Minimum value is ${min}`); return false; } } if (input.hasAttribute('max') && type === 'number') { const max = parseFloat(input.getAttribute('max') || '0'); if (parseFloat(value) > max) { this.setFieldError(input, `Maximum value is ${max}`); return false; } } // Check type-specific validation if (type === 'email' && value) { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (!emailRegex.test(value)) { this.setFieldError(input, 'Please enter a valid email address'); return false; } } if (type === 'url' && value) { try { new URL(value); } catch (_a) { this.setFieldError(input, 'Please enter a valid URL'); return false; } } // Check custom rules if (this.options.rules && name && this.options.rules[name]) { const rule = this.options.rules[name]; if (rule.required && !value) { this.setFieldError(input, rule.message || 'This field is required'); return false; } if (rule.min !== undefined) { const numValue = type === 'number' ? parseFloat(value) : value.length; if (numValue < rule.min) { this.setFieldError(input, rule.message || `Minimum ${type === 'number' ? 'value' : 'length'} is ${rule.min}`); return false; } } if (rule.max !== undefined) { const numValue = type === 'number' ? parseFloat(value) : value.length; if (numValue > rule.max) { this.setFieldError(input, rule.message || `Maximum ${type === 'number' ? 'value' : 'length'} is ${rule.max}`); return false; } } if (rule.pattern && value) { if (!rule.pattern.test(value)) { this.setFieldError(input, rule.message || 'Invalid format'); return false; } } if (rule.custom && !rule.custom(value)) { this.setFieldError(input, rule.message || 'Invalid value'); return false; } } return true; } /** * Set field error */ setFieldError(input, message) { var _a, _b; this.errors.set(input.name, message); addClass(input, 'error'); if (this.options.showErrors) { // Remove any existing error message const existingError = (_a = input.parentElement) === null || _a === void 0 ? void 0 : _a.querySelector('.error-message'); if (existingError) { existingError.remove(); } // Create and insert error message const errorEl = createElement('span', { className: 'error-message', text: message, style: { color: 'var(--apple-error)', fontSize: 'var(--apple-text-sm)', marginTop: 'var(--apple-space-1)', display: 'block' } }); (_b = input.parentElement) === null || _b === void 0 ? void 0 : _b.appendChild(errorEl); } } /** * Clear field error */ clearFieldError(input) { var _a; this.errors.delete(input.name); removeClass(input, 'error'); const errorEl = (_a = input.parentElement) === null || _a === void 0 ? void 0 : _a.querySelector('.error-message'); if (errorEl) { errorEl.remove(); } } /** * Clear all errors */ clearErrors() { this.errors.clear(); const inputs = this.getInputs(); inputs.forEach(input => { var _a; removeClass(input, 'error'); const errorEl = (_a = input.parentElement) === null || _a === void 0 ? void 0 : _a.querySelector('.error-message'); if (errorEl) { errorEl.remove(); } }); } /** * Get all errors */ getErrors() { return new Map(this.errors); } /** * Set custom rules */ setRules(rules) { this.options.rules = rules; } /** * Add custom validator */ addValidator(name, rule) { if (!this.options.rules) { this.options.rules = {}; } this.options.rules[name] = rule; } /** * Static helper to validate form */ static validateForm(selector) { const form = $(selector); if (!form || !(form instanceof HTMLFormElement)) return false; const inputs = form.querySelectorAll('input[required], textarea[required], select[required]'); let isValid = true; inputs.forEach(input => { const el = input; if (!el.value.trim()) { isValid = false; addClass(el, 'error'); // Remove error class on input const inputHandler = () => { removeClass(el, 'error'); }; el.addEventListener('input', inputHandler, { once: true }); } }); return isValid; } } //# sourceMappingURL=form.js.map