UNPKG

stylescape

Version:

Stylescape is a visual identity framework developed by Scape Agency.

234 lines 8.48 kB
export class FormValidator { constructor(formSelectorOrElement, options = {}) { this.rules = new Map(); this.errors = new Map(); this.handleSubmit = (e) => { e.preventDefault(); if (this.validate() && this.form) { this.options.onValid(this.form); } }; this.handleFieldInput = (e) => { this.validateField(e.target); }; this.handleFieldBlur = (e) => { this.validateField(e.target); }; this.form = typeof formSelectorOrElement === "string" ? document.querySelector(formSelectorOrElement) : formSelectorOrElement; this.options = { validateOnInput: options.validateOnInput !== false, validateOnBlur: options.validateOnBlur !== false, showInlineErrors: options.showInlineErrors !== false, invalidClass: options.invalidClass ?? "input--invalid", validClass: options.validClass ?? "input--valid", errorClass: options.errorClass ?? "input__error", onValid: options.onValid ?? (() => { }), onInvalid: options.onInvalid ?? (() => { }), onFieldValidate: options.onFieldValidate ?? (() => { }), }; if (!this.form) { console.warn("[Stylescape] FormValidator form not found"); return; } this.init(); } addRule(fieldName, rule) { const existing = this.rules.get(fieldName) || []; existing.push(rule); this.rules.set(fieldName, existing); } removeRules(fieldName) { this.rules.delete(fieldName); } validate() { this.errors.clear(); let isValid = true; this.getFields().forEach((field) => { if (!this.validateField(field)) { isValid = false; } }); if (isValid) { if (this.form) this.options.onValid(this.form); } else { this.options.onInvalid(this.errors); } return isValid; } validateField(field) { const fieldName = field.name; const value = field.value.trim(); const rules = this.rules.get(fieldName) || []; const fieldErrors = []; const dataRules = this.parseDataRules(field); const allRules = [...rules, ...dataRules]; for (const rule of allRules) { if (!this.checkRule(rule, value, field)) { fieldErrors.push(rule.message); } } if (fieldErrors.length > 0) { this.errors.set(fieldName, fieldErrors); } else { this.errors.delete(fieldName); } this.updateFieldUI(field, fieldErrors); this.options.onFieldValidate(field, fieldErrors.length === 0, fieldErrors); return fieldErrors.length === 0; } getErrors() { return new Map(this.errors); } getFieldErrors(fieldName) { return this.errors.get(fieldName) || []; } clear() { this.errors.clear(); this.getFields().forEach((field) => { field.classList.remove(this.options.invalidClass, this.options.validClass); this.removeErrorMessage(field); }); } destroy() { this.form?.removeEventListener("submit", this.handleSubmit); this.getFields().forEach((field) => { field.removeEventListener("input", this.handleFieldInput); field.removeEventListener("blur", this.handleFieldBlur); }); this.form = null; this.rules.clear(); this.errors.clear(); } init() { if (!this.form) return; this.form.addEventListener("submit", this.handleSubmit); this.getFields().forEach((field) => { if (this.options.validateOnInput) { field.addEventListener("input", this.handleFieldInput); } if (this.options.validateOnBlur) { field.addEventListener("blur", this.handleFieldBlur); } }); this.form.setAttribute("novalidate", ""); } getFields() { if (!this.form) return []; return Array.from(this.form.querySelectorAll("input:not([type='submit']):not([type='button']), textarea, select")); } parseDataRules(field) { const rules = []; const prefix = "data-ss-validate-"; if (field.hasAttribute(`${prefix}required`)) { rules.push({ type: "required", message: field.getAttribute(`${prefix}required`) || "This field is required", }); } if (field.hasAttribute(`${prefix}email`)) { rules.push({ type: "email", message: field.getAttribute(`${prefix}email`) || "Invalid email address", }); } const minLength = field.getAttribute(`${prefix}min-length`); if (minLength) { rules.push({ type: "minLength", value: parseInt(minLength, 10), message: field.getAttribute(`${prefix}min-length-message`) || `Minimum ${minLength} characters required`, }); } const maxLength = field.getAttribute(`${prefix}max-length`); if (maxLength) { rules.push({ type: "maxLength", value: parseInt(maxLength, 10), message: field.getAttribute(`${prefix}max-length-message`) || `Maximum ${maxLength} characters allowed`, }); } const pattern = field.getAttribute(`${prefix}pattern`); if (pattern) { rules.push({ type: "pattern", value: new RegExp(pattern), message: field.getAttribute(`${prefix}pattern-message`) || "Invalid format", }); } const match = field.getAttribute(`${prefix}match`); if (match) { rules.push({ type: "match", value: match, message: field.getAttribute(`${prefix}match-message`) || "Fields do not match", }); } return rules; } checkRule(rule, value, field) { switch (rule.type) { case "required": return value.length > 0; case "email": return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value); case "minLength": return value.length >= rule.value; case "maxLength": return value.length <= rule.value; case "pattern": return rule.value.test(value); case "match": { const matchField = this.form?.querySelector(`[name="${rule.value}"]`); return matchField ? value === matchField.value : false; } case "custom": return rule.validator ? rule.validator(value, field) : true; default: return true; } } updateFieldUI(field, errors) { const isValid = errors.length === 0; field.classList.toggle(this.options.invalidClass, !isValid); field.classList.toggle(this.options.validClass, isValid && field.value.length > 0); field.setAttribute("aria-invalid", String(!isValid)); if (this.options.showInlineErrors) { this.removeErrorMessage(field); if (!isValid) { this.showErrorMessage(field, errors[0]); } } } showErrorMessage(field, message) { const errorId = `${field.name}-error`; const errorEl = document.createElement("div"); errorEl.id = errorId; errorEl.className = this.options.errorClass; errorEl.textContent = message; errorEl.setAttribute("role", "alert"); field.setAttribute("aria-describedby", errorId); field.parentNode?.insertBefore(errorEl, field.nextSibling); } removeErrorMessage(field) { const errorId = `${field.name}-error`; const errorEl = document.getElementById(errorId); errorEl?.remove(); field.removeAttribute("aria-describedby"); } } export default FormValidator; //# sourceMappingURL=FormValidator.js.map