UNPKG

svelte-forms-lib

Version:

Svelte forms lib - A lightweight library for managing forms in Svelte v3

256 lines (216 loc) 6.39 kB
import {derived, writable, get} from 'svelte/store'; import {util} from './util'; const NO_ERROR = ''; const IS_TOUCHED = true; function isCheckbox(element) { return element.getAttribute && element.getAttribute('type') === 'checkbox'; } function isFileInput(element) { return element.getAttribute && element.getAttribute('type') === 'file'; } function resolveValue(element) { if (isFileInput(element)) { return element.files; } else if (isCheckbox(element)) { return element.checked; } else { return element.value; } } export const createForm = (config) => { let initialValues = config.initialValues || {}; const validationSchema = config.validationSchema; const validateFunction = config.validate; const onSubmit = config.onSubmit; const getInitial = { values: () => util.cloneDeep(initialValues), errors: () => validationSchema ? util.getErrorsFromSchema(initialValues, validationSchema.fields) : util.assignDeep(initialValues, NO_ERROR), touched: () => util.assignDeep(initialValues, !IS_TOUCHED), }; const form = writable(getInitial.values()); const errors = writable(getInitial.errors()); const touched = writable(getInitial.touched()); const isSubmitting = writable(false); const isValidating = writable(false); const isValid = derived(errors, ($errors) => { const noErrors = util .getValues($errors) .every((field) => field === NO_ERROR); return noErrors; }); const modified = derived(form, ($form) => { const object = util.assignDeep($form, false); for (let key in $form) { object[key] = !util.deepEqual($form[key], initialValues[key]); } return object; }); const isModified = derived(modified, ($modified) => { return util.getValues($modified).includes(true); }); function validateField(field) { return util .subscribeOnce(form) .then((values) => validateFieldValue(field, values[field])); } function validateFieldValue(field, value) { updateTouched(field, true); if (validationSchema) { isValidating.set(true); return validationSchema .validateAt(field, get(form)) .then(() => util.update(errors, field, '')) .catch((error) => util.update(errors, field, error.message)) .finally(() => { isValidating.set(false); }); } if (validateFunction) { isValidating.set(true); return Promise.resolve() .then(() => validateFunction({[field]: value})) .then((errs) => util.update(errors, field, !util.isNullish(errs) ? errs[field] : ''), ) .finally(() => { isValidating.set(false); }); } return Promise.resolve(); } function updateValidateField(field, value) { updateField(field, value); return validateFieldValue(field, value); } function handleChange(event) { const element = event.target; const field = element.name || element.id; const value = resolveValue(element); return updateValidateField(field, value); } function handleSubmit(event) { if (event && event.preventDefault) { event.preventDefault(); } isSubmitting.set(true); return util.subscribeOnce(form).then((values) => { if (typeof validateFunction === 'function') { isValidating.set(true); return Promise.resolve() .then(() => validateFunction(values)) .then((error) => { if (util.isNullish(error) || util.getValues(error).length === 0) { return clearErrorsAndSubmit(values); } else { errors.set(error); isSubmitting.set(false); } }) .finally(() => isValidating.set(false)); } if (validationSchema) { isValidating.set(true); return ( validationSchema .validate(values, {abortEarly: false}) .then(() => clearErrorsAndSubmit(values)) // eslint-disable-next-line unicorn/catch-error-name .catch((yupErrors) => { if (yupErrors && yupErrors.inner) { const updatedErrors = getInitial.errors(); yupErrors.inner.map((error) => util.set(updatedErrors, error.path, error.message), ); errors.set(updatedErrors); } isSubmitting.set(false); }) .finally(() => isValidating.set(false)) ); } return clearErrorsAndSubmit(values); }); } function handleReset() { form.set(getInitial.values()); errors.set(getInitial.errors()); touched.set(getInitial.touched()); } function clearErrorsAndSubmit(values) { return Promise.resolve() .then(() => errors.set(getInitial.errors())) .then(() => onSubmit(values, form, errors)) .finally(() => isSubmitting.set(false)); } /** * Handler to imperatively update the value of a form field */ function updateField(field, value) { util.update(form, field, value); } /** * Handler to imperatively update the touched value of a form field */ function updateTouched(field, value) { util.update(touched, field, value); } /** * Update the initial values and reset form. Used to dynamically display new form values */ function updateInitialValues(newValues) { initialValues = newValues; handleReset(); } return { form, errors, touched, modified, isValid, isSubmitting, isValidating, isModified, handleChange, handleSubmit, handleReset, updateField, updateValidateField, updateTouched, validateField, updateInitialValues, state: derived( [ form, errors, touched, modified, isValid, isValidating, isSubmitting, isModified, ], ([ $form, $errors, $touched, $modified, $isValid, $isValidating, $isSubmitting, $isModified, ]) => ({ form: $form, errors: $errors, touched: $touched, modified: $modified, isValid: $isValid, isSubmitting: $isSubmitting, isValidating: $isValidating, isModified: $isModified, }), ), }; };