UNPKG

@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
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); }