formValidator = {
const { getValues, setValues, listenValues } = formAccessor;
function updateResets(_form, element, info) {
element.disabled = !info.dirty;
}
function updateSubmits(_form, element, info) {
element.disabled = !info.valid;
}
function updateErrors(form, errors) {
[...form.querySelectorAll(`[data--form-error]`)].forEach((errorElement) => {
errorElement.innerHTML = "";
errorElement.classList.remove("error");
errorElement.style.display = "none";
});
for (const [name, error] of Object.entries(errors)) {
const errorElement = form.querySelector(`[data--form-error="${name}"]`);
if (!error || !errorElement) continue;
errorElement.innerHTML =
typeof error === "object" ? error.message : error;
errorElement.classList.add("error");
errorElement.style.display = "";
}
}
function deepEqual(a, b) {
if (a === b) return true;
if (typeof a !== "object" || typeof b !== "object") return false;
if (a === null || b === null) return false;
if (Array.isArray(a) && Array.isArray(b)) {
if (a.length !== b.length) return false;
const len = a.length;
for (let i = 0; i < len; i++) {
if (!deepEqual(a[i], b[i])) return false;
}
} else {
const keysA = Object.keys(a);
const keysB = Object.keys(b);
if (keysA.length !== keysB.length) return false;
for (const key of keysA) {
if (!deepEqual(a[key], b[key])) return false;
}
}
return true;
}
function formValidator(
form,
{
value,
onSubmit = () => {},
onUpdate = () => {},
validate = () => {},
updateResetView = updateResets,
updateSubmitView = updateSubmits,
updateErrorViews = updateErrors,
submitSelector = '[type="submit"]',
resetSelector = '[type="button"]',
equal = deepEqual
} = {}
) {
const submitRegistrations = [
...form.querySelectorAll(submitSelector)
].map((button) => addButtonListener(button, "click", handleSubmit));
const resetRegistrations = [
...form.querySelectorAll(resetSelector)
].map((button) => addButtonListener(button, "click", handleReset));
value = value || getFormValues();
setValues(form, value);
const formListenerRegistration = listenValues(form, (values) => {
_updateFormStateView(values, true);
});
return {
resetForm,
getFormValues,
getFormState,
unregister
};
function addEventListener(element, event, handler) {
element.addEventListener(event, handler);
return () => element.removeEventListener(event, handler);
}
function addButtonListener(element, event, handler) {
return { element, unregister: addEventListener(element, event, handler) };
}
function handleSubmit(event) {
const element = event.target;
event.preventDefault();
event.stopPropagation();
const formState = getFormState();
formState.submit = true;
if (formState.valid) {
resetForm((value = formState.values));
formState.dirty = false;
}
onSubmit(formState, element);
}
function handleReset(event) {
event.preventDefault();
resetForm();
}
function buildFormState(values) {
const errors = validate(values) || {};
const dirty = !equal(values, value);
return {
errors,
values,
valid: Object.keys(errors).length === 0,
submit: false,
dirty
};
}
function _updateFormStateView(values, notify) {
const formState = buildFormState(values);
updateErrorViews(form, formState.errors);
submitRegistrations.forEach(({ element }) =>
updateSubmitView(form, element, formState)
);
resetRegistrations.forEach(({ element }) =>
updateResetView(form, element, formState)
);
notify && onUpdate(formState);
}
function setFormValues(values, notify) {
setValues(form, values);
values = getFormValues();
_updateFormStateView(values, notify);
}
function resetForm(values) {
setFormValues((value = values || value), true);
}
function getFormValues() {
return getValues(form);
}
function getFormState() {
const values = getFormValues();
return buildFormState(values);
}
function unregister() {
formListenerRegistration();
submitRegistrations.forEach((r) => r.unregister());
resetRegistrations.forEach((r) => r.unregister());
}
}
return formValidator;
}