@tuentyfaiv/svelte-form
Version:
A form library for Svelte. It is built on top of Svelte and Typescript. Inspired by Formik and React Hook Form.
143 lines (142 loc) • 5.14 kB
JavaScript
import { getContext, hasContext, setContext } from "svelte";
import { get, readable, writable } from "svelte/store";
import { FormError } from "../utils/errors.js";
import { parseValue } from "../utils/parse.js";
import { bridge } from "../utils/validation.js";
import { Adapter } from "../typing/stores/form.js";
export function faivform({ fields, context = "form", styles = {}, }) {
let form = null;
const ctxStyles = writable({
replace: styles.replace ?? false,
field: styles.field ?? {},
option: styles.option ?? {},
select: styles.select ?? {},
file: styles.file ?? {},
errors: styles.errors ?? {},
});
const adapter = fields instanceof Adapter ? fields : bridge(fields);
const initial = adapter.initial();
const loading = writable(false);
const errors = writable(initial.errors);
const data = writable(initial.fields);
function toggleLoading(value) {
if (typeof value === "boolean") {
loading.set(value);
}
else {
loading.update((prev) => !prev);
}
}
async function setError(field, error = null) {
errors.update((prev) => ({ ...prev, [field]: error }));
}
function setErrors({ error, handle }) {
adapter.errors(error, errors, handle);
}
async function setField(field, value, validate = true) {
data.update((prev) => ({ ...prev, [field]: value }));
if (typeof value === "undefined" && !validate) {
setError(field);
return;
}
if (validate) {
await adapter.field(field, value, errors);
}
}
async function check(event) {
const { name } = event.currentTarget;
const value = parseValue(event.currentTarget);
data.update((prev) => ({ ...prev, [name]: value }));
await adapter.field(name, value, errors);
}
async function validation() {
const formData = form ? Object.fromEntries(new FormData(form).entries()) : {};
const allData = { ...formData, ...get(data) };
await adapter.validate(allData);
return allData;
}
function resetForm(clear = true, starting = initial.fields) {
form?.reset();
data.set(starting);
if (clear) {
errors.set(initial.errors);
}
}
function submit(action, { reset = true, ...config } = {}) {
if (config.initial)
data.set(config.initial);
async function onSubmit(event) {
try {
toggleLoading(true);
if (event)
form = event.currentTarget;
const values = await validation();
await action(values);
if (reset)
resetForm(true, config.initial);
}
catch (error) {
setErrors({ error, handle: config.error });
}
finally {
toggleLoading(false);
config.finish?.();
}
}
return onSubmit;
}
const contextform = readable({
loading,
errors,
data,
styles: ctxStyles,
reset: resetForm,
setError,
setField,
check,
submit,
});
setContext(context, contextform);
if (hasContext("faivform-styles")) {
const globalStyles = get(getContext("faivform-styles"));
const formStyles = get(ctxStyles);
const updatedStyles = Object.entries(globalStyles).reduce((acc, [key, value]) => {
const keyStyle = key;
const globalStyle = value;
const formStyle = formStyles[keyStyle];
if (typeof globalStyle === "undefined" || globalStyle === null || Object.keys(globalStyle ?? {}).length === 0)
return acc;
if (keyStyle === "replace" || typeof globalStyle === "boolean" || typeof formStyle === "boolean") {
return {
...acc,
[keyStyle]: formStyle || globalStyle,
};
}
const updated = Object.entries(globalStyle).reduce((prev, [element, style]) => {
const elementKey = element;
const elementStyle = style;
const formElementStyle = formStyle?.[elementKey];
return {
...prev,
[element]: formElementStyle ?? elementStyle,
};
}, {});
return {
...acc,
[keyStyle]: updated,
};
}, {});
ctxStyles.update((prev) => ({ ...prev, ...updatedStyles }));
}
return getContext(context);
}
export function useForm(context = "form") {
if (!hasContext(context)) {
throw new FormError({
title: "Unknow form context",
message: `Error to get form context: ${context}. You must create the form context in a parent component using the faivform function.`,
reason: `The context "${context}" does not exist.`,
});
}
return getContext(context);
}