stylescape
Version:
Stylescape is a visual identity framework developed by Scape Agency.
234 lines • 8.48 kB
JavaScript
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