From a398882e13cd1c837718af8d09a83f63c5ec5ca1 Mon Sep 17 00:00:00 2001 From: Danielo Rodriguez Date: Mon, 13 May 2024 16:34:45 +0200 Subject: [PATCH] fix(formEngine): gracefully handle the close of the form --- src/FormModal.ts | 15 +++++--- src/store/formStore.ts | 81 +++++++++++++++++------------------------- 2 files changed, 44 insertions(+), 52 deletions(-) diff --git a/src/FormModal.ts b/src/FormModal.ts index ef6c3b57..80e22f06 100644 --- a/src/FormModal.ts +++ b/src/FormModal.ts @@ -41,10 +41,17 @@ export class FormModal extends Modal { modalDefinition.fields, options?.values ?? {}, ); - this.formEngine = makeFormEngine((result) => { - this.onSubmit(FormResult.make(result, "ok")); - this.close(); - }, this.initialFormValues); + this.formEngine = makeFormEngine({ + onSubmit: (result) => { + this.onSubmit(FormResult.make(result, "ok")); + this.close(); + }, + onCancel: () => { + this.onSubmit(FormResult.make({}, "cancelled")); + this.close(); + }, + defaultValues: this.initialFormValues, + }); // this.formEngine.subscribe(console.log); } diff --git a/src/store/formStore.ts b/src/store/formStore.ts index fa6cea93..fd0ab601 100644 --- a/src/store/formStore.ts +++ b/src/store/formStore.ts @@ -2,13 +2,13 @@ // and the errors that are present in the form. It is used by the Form component to render the form and to update the import { NonEmptyArray, pipe } from "@std"; +import * as A from "fp-ts/Array"; import * as E from "fp-ts/Either"; -import { absurd } from "fp-ts/function"; import * as O from "fp-ts/Option"; -import * as A from "fp-ts/Array"; -import { Writable, derived, writable, Readable, get } from "svelte/store"; -import { fromEntries, toEntries } from "fp-ts/Record"; import { Option } from "fp-ts/Option"; +import { fromEntries, toEntries } from "fp-ts/Record"; +import { absurd } from "fp-ts/function"; +import { Readable, Writable, derived, get, writable } from "svelte/store"; type Rule = { tag: "required"; message: string }; //| { tag: 'minLength', length: number, message: string } | { tag: 'maxLength', length: number, message: string } | { tag: 'pattern', pattern: RegExp, message: string }; function requiredRule(fieldName: string, message?: string): Rule { @@ -27,10 +27,7 @@ type FieldFailed = { rules: Rule; errors: NonEmptyArray; }; -function FieldFailed( - field: Field, - failedRule: Rule, -): FieldFailed { +function FieldFailed(field: Field, failedRule: Rule): FieldFailed { return { ...field, rules: failedRule, errors: [failedRule.message] }; } type FormStore = { fields: Record> }; @@ -45,11 +42,10 @@ export interface FormEngine { * Use them to bind the field to the form and be notified of errors. * @param field a field definition to start tracking */ - addField: (field: { - name: string; - label?: string; - isRequired?: boolean; - }) => { value: Writable; errors: Readable }; + addField: (field: { name: string; label?: string; isRequired?: boolean }) => { + value: Writable; + errors: Readable; + }; /** * Subscribes to the form store. This method is required to conform to the svelte store interface. */ @@ -73,11 +69,7 @@ function nonEmptyValue(s: FieldValue): Option { case "boolean": return O.some(s); case "object": - return Array.isArray(s) - ? s.length > 0 - ? O.some(s) - : O.none - : O.none; + return Array.isArray(s) ? (s.length > 0 ? O.some(s) : O.none) : O.none; default: return absurd(s); } @@ -88,9 +80,7 @@ function nonEmptyValue(s: FieldValue): Option { * If the field meets the requirements, the field is returned as is in a right. * If the field does not meet the requirements, the field is returned with the errors in a left. */ -function parseField( - field: Field, -): E.Either, Field> { +function parseField(field: Field): E.Either, Field> { if (!field.rules) return E.right(field); const rule = field.rules; switch (rule.tag) { @@ -100,8 +90,7 @@ function parseField( O.chain(nonEmptyValue), O.match( () => E.left(FieldFailed(field, rule)), - (value) => - E.right(field) + (value) => E.right(field), ), ); default: @@ -139,11 +128,16 @@ function parseForm( } export type OnFormSubmit = (values: Record) => void; +type makeFormEngineArgs = { + onSubmit: OnFormSubmit; + onCancel?: () => void; + defaultValues: Record; +}; -export function makeFormEngine( - onSubmit: OnFormSubmit, - defaultValues: Record = {}, -): FormEngine { +export function makeFormEngine({ + onSubmit, + defaultValues, +}: makeFormEngineArgs): FormEngine { const formStore: Writable> = writable({ fields: {} }); // Creates helper functions to modify the store immutably function setFormField(name: string) { @@ -205,23 +199,17 @@ export function makeFormEngine( ), triggerSubmit() { const formState = get(formStore); + // prettier-ignore pipe( - formState.fields, - parseForm, - E.match(setErrors, onSubmit)); + formState.fields, + parseForm, + E.match(setErrors, onSubmit) + ); }, addField: (field) => { const { initField: setField, setValue } = setFormField(field.name); - setField( - [], - field.isRequired - ? requiredRule(field.label || field.name) - : undefined, - ); - const fieldStore = derived( - formStore, - ({ fields }) => fields[field.name], - ); + setField([], field.isRequired ? requiredRule(field.label || field.name) : undefined); + const fieldStore = derived(formStore, ({ fields }) => fields[field.name]); const fieldValueStore: Writable = { subscribe(cb) { return fieldStore.subscribe((x) => @@ -240,14 +228,14 @@ export function makeFormEngine( formStore.update((form) => { const current = form.fields[field.name]; if (!current) { - console.error( - new Error(`Field ${field.name} does not exist`), - ); + console.error(new Error(`Field ${field.name} does not exist`)); return form; } const newValue = pipe( + // fuck prettier current.value, - O.map(updater)); + O.map(updater), + ); return { ...form, fields: { @@ -264,10 +252,7 @@ export function makeFormEngine( }; return { value: fieldValueStore, - errors: derived( - formStore, - ({ fields }) => fields[field.name]?.errors ?? [], - ), + errors: derived(formStore, ({ fields }) => fields[field.name]?.errors ?? []), }; }, };