const rules = {
required: (v) => v.trim() !== '' || 'This field is required.',
email: (v) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v) || 'Enter a valid email.',
min: (n) => (v) => v.length >= n || `Minimum ${n} characters required.`,
max: (n) => (v) => v.length <= n || `Maximum ${n} characters allowed.`,
url: (v) => {
try { new URL(v); return true; }
catch { return 'Enter a valid URL.'; }
},
};
function validate(value, ...validators) {
for (const fn of validators) {
const result = fn(value);
if (result !== true) return { valid: false, error: result };
}
return { valid: true, error: null };
}
function validateForm(form) {
let isValid = true;
form.querySelectorAll('[data-validate]').forEach(field => {
const specs = field.dataset.validate.split(' ');
const fns = specs.map(s => {
const [name, arg] = s.split(':');
return arg ? rules[name](+arg) : rules[name];
}).filter(Boolean);
const { valid, error } = validate(field.value, ...fns);
const errEl = document.getElementById(`${field.id}-error`);
field.setAttribute('aria-invalid', String(!valid));
if (errEl) errEl.textContent = error ?? '';
if (!valid) isValid = false;
});
return isValid;
}