Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/components/Field.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
{name}
type={type}
value={$form[name]}
checked={$form[name]}
on:change={handleChange}
on:blur={handleChange}
{...$$props} />
11 changes: 11 additions & 0 deletions lib/components/Submit.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<script>
import { getContext } from "svelte";
import { key } from "./key";

const { state } = getContext(key);

</script>

<button {...$$props} type="submit" disabled={!$state.isValid}>
<slot />
</button>
316 changes: 161 additions & 155 deletions lib/createForm.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,168 +5,174 @@ const NO_ERROR = "";
const IS_TOUCHED = true;

export const createForm = config => {
const initialValues = config.initialValues || {};

if (Object.keys(initialValues).length < 1) {
const provided = JSON.stringify(initialValues);
console.warn(
`createForm requires initialValues to be a non empty object or array, provided ${provided}`
);
return;
}

const validationSchema = config.validationSchema;
const validateFn = config.validate;
const onSubmit = config.onSubmit;

const initial = {
values: () => util.cloneDeep(initialValues),
errors: () => util.assignDeep(initialValues, NO_ERROR),
touched: () => util.assignDeep(initialValues, !IS_TOUCHED)
};

const form = writable(initial.values());
const errors = writable(initial.errors());
const touched = writable(initial.touched());

const isSubmitting = writable(false);
const isValidating = writable(false);

const isValid = derived([errors, touched], ([$errors, $touched]) => {
const allTouched = util
.getValues($touched)
.every(field => field === IS_TOUCHED);
const noErrors = util.getValues($errors).every(field => field === NO_ERROR);
return allTouched && noErrors;
});

function isCheckbox(el) {
return el.getAttribute && el.getAttribute('type') === 'checkbox';
}

function handleChange(event) {
const el = event.target;
const field = el.name;
const value = isCheckbox(el) ? el.checked : el.value;

updateTouched(field, true);

if (validationSchema) {
isValidating.set(true);
return util
.reach(validationSchema, field)
.validate(value)
.then(() => util.update(errors, field, ""))
.catch(err => util.update(errors, field, err.message))
.finally(() => {
updateField(field, value);
isValidating.set(false);
});
const initialValues = config.initialValues || {};

if (Object.keys(initialValues).length < 1) {
const provided = JSON.stringify(initialValues);
console.warn(
`createForm requires initialValues to be a non empty object or array, provided ${provided}`
);
return;
}

if (validateFn) {
isValidating.set(true);
return Promise.resolve()
.then(() => validateFn({ [field]: value }))
.then(errs => util.update(errors, field, errs[field]))
.finally(() => {
updateField(field, value);
isValidating.set(false);
})
}
const validationSchema = config.validationSchema;
const validateFn = config.validate;
const onSubmit = config.onSubmit;

const initial = {
values: () => util.cloneDeep(initialValues),
errors: () => util.assignDeep(initialValues, NO_ERROR),
touched: () => util.assignDeep(initialValues, !IS_TOUCHED)
};

const form = writable(initial.values());
const errors = writable(initial.errors());
const touched = writable(initial.touched());

let context;
form.subscribe(val => context = val);

const isSubmitting = writable(false);
const isValidating = writable(false);

const isValid = derived([errors, touched], ([$errors, $touched]) => {
/*
const allTouched = util
.getValues($touched)
.every(field => field === IS_TOUCHED);
*/
const noErrors = util.getValues($errors).every(field => field === NO_ERROR);
return /* allTouched && */ noErrors;
});

updateField(field, value);
}
function isCheckbox(el) {
return el.getAttribute && el.getAttribute('type') === 'checkbox';
}

function handleSubmit(ev) {
if (ev && ev.preventDefault) {
ev.preventDefault();
function handleChange(event) {
const el = event.target;
const field = el.name;
const value = isCheckbox(el) ? el.checked : el.value;

updateTouched(field, true);

if (validationSchema) {
isValidating.set(true);
return validationSchema
.validateAt(field, context)
.then(() => util.update(errors, field, ""))
.catch(err => util.update(errors, field, err.message))
.finally(() => {
updateField(field, value);
isValidating.set(false);
});
}

if (validateFn) {
isValidating.set(true);
return Promise.resolve()
.then(() => validateFn({
[field]: value
}))
.then(errs => util.update(errors, field, errs[field]))
.finally(() => {
updateField(field, value);
isValidating.set(false);
})
}

updateField(field, value);
}

isSubmitting.set(true);
function handleSubmit(ev) {
if (ev && ev.preventDefault) {
ev.preventDefault();
}

return util.subscribeOnce(form).then(values => {
if (typeof validateFn === "function") {
isValidating.set(true);
isSubmitting.set(true);

return Promise.resolve()
.then(() => validateFn(values))
.then(err =>
util.isEmpty(err) ? clearErrorsAndSubmit(values) : errors.set(err)
)
.finally(() => isValidating.set(false));
}

if (validationSchema) {
isValidating.set(true);

return validationSchema
.validate(values, { abortEarly: false })
.then(() => clearErrorsAndSubmit(values))
.catch(yupErrs => {
if (yupErrs && yupErrs.inner) {
yupErrs.inner.forEach(error =>
util.update(errors, error.path, error.message)
);
return util.subscribeOnce(form).then(values => {
if (typeof validateFn === "function") {
isValidating.set(true);

return Promise.resolve()
.then(() => validateFn(values))
.then(err =>
util.isEmpty(err) ? clearErrorsAndSubmit(values) : errors.set(err)
)
.finally(() => isValidating.set(false));
}
isSubmitting.set(false);
})
.finally(() => isValidating.set(false));
}

clearErrorsAndSubmit(values);
});
}

function handleReset() {
form.set(initial.values());
errors.set(initial.errors());
touched.set(initial.touched());
}

function clearErrorsAndSubmit(values) {
return Promise.resolve()
.then(() => errors.set(util.assignDeep(values, "")))
.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);
}

return {
form,
errors,
touched,
isValid,
isSubmitting,
isValidating,
handleChange,
handleSubmit,
handleReset,
updateField,
updateTouched,
state: derived(
[form, errors, touched, isValid, isValidating, isSubmitting],
([$form, $errors, $touched, $isValid, $isValidating, $isSubmitting]) => ({
form: $form,
errors: $errors,
touched: $touched,
isValid: $isValid,
isSubmitting: $isSubmitting,
isValidating: $isValidating
})
)
};
};
if (validationSchema) {
isValidating.set(true);

return validationSchema
.validate(values, { abortEarly: false })
.then(() => clearErrorsAndSubmit(values))
.catch(yupErrs => {
if (yupErrs && yupErrs.inner) {
yupErrs.inner.forEach(error =>
util.update(errors, error.path, error.message)
);
}
isSubmitting.set(false);
})
.finally(() => isValidating.set(false));
}

clearErrorsAndSubmit(values);
});
}

function handleReset() {
form.set(initial.values());
errors.set(initial.errors());
touched.set(initial.touched());
}

function clearErrorsAndSubmit(values) {
return Promise.resolve()
.then(() => errors.set(util.assignDeep(values, "")))
.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);
}

return {
form,
errors,
touched,
isValid,
isSubmitting,
isValidating,
handleChange,
handleSubmit,
handleReset,
updateField,
updateTouched,
state: derived(
[form, errors, touched, isValid, isValidating, isSubmitting],
([$form, $errors, $touched, $isValid, $isValidating, $isSubmitting]) => ({
form: $form,
errors: $errors,
touched: $touched,
isValid: $isValid,
isSubmitting: $isSubmitting,
isValidating: $isValidating
})
)
};
};
17 changes: 12 additions & 5 deletions lib/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
export { createForm } from "./createForm";
export { default as Form } from "./components/Form.svelte";
export { default as Field } from "./components/Field.svelte";
export { default as Select } from "./components/Select.svelte";
export { default as ErrorMessage } from "./components/ErrorMessage.svelte";
export { createForm }
from "./createForm";
export { default as Form }
from "./components/Form.svelte";
export { default as Field }
from "./components/Field.svelte";
export { default as Select }
from "./components/Select.svelte";
export { default as ErrorMessage }
from "./components/ErrorMessage.svelte";
export { default as Submit }
from "./components/Submit.svelte";