From 6fcfe73ccf73e4e16882ae1658891a3c32499489 Mon Sep 17 00:00:00 2001 From: Lars Berger Date: Sat, 26 Oct 2024 18:26:20 +0800 Subject: [PATCH] feat: rename methods to improve clarity of field-level or form-level --- README.md | 28 +++++ src/components/field.component.tsx | 28 ++--- .../{get-error.ts => get-field-error.ts} | 2 +- .../{get-value.ts => get-field-value.ts} | 6 +- src/methods/index.ts | 14 ++- src/methods/is-dirty.ts | 35 +----- src/methods/is-field-dirty.ts | 35 ++++++ src/methods/is-field-invalid.ts | 35 ++++++ src/methods/is-field-touched.ts | 35 ++++++ src/methods/is-invalid.ts | 35 +----- src/methods/is-touched.ts | 35 +----- .../{set-dirty.ts => set-field-dirty.ts} | 8 +- .../{set-touched.ts => set-field-touched.ts} | 13 +-- src/methods/set-field-value.ts | 68 +++++++++++ src/methods/set-value.ts | 110 ++---------------- src/methods/unset-dirty.ts | 28 +---- src/methods/unset-field-dirty.ts | 27 +++++ src/methods/unset-field-touched.ts | 27 +++++ src/methods/unset-touched.ts | 26 +---- src/primitives/create-form.ts | 42 +++---- src/types/form-state.model.ts | 48 ++++---- 21 files changed, 372 insertions(+), 313 deletions(-) rename src/methods/{get-error.ts => get-field-error.ts} (72%) rename src/methods/{get-value.ts => get-field-value.ts} (74%) create mode 100644 src/methods/is-field-dirty.ts create mode 100644 src/methods/is-field-invalid.ts create mode 100644 src/methods/is-field-touched.ts rename src/methods/{set-dirty.ts => set-field-dirty.ts} (74%) rename src/methods/{set-touched.ts => set-field-touched.ts} (68%) create mode 100644 src/methods/set-field-value.ts create mode 100644 src/methods/unset-field-dirty.ts create mode 100644 src/methods/unset-field-touched.ts diff --git a/README.md b/README.md index ee168e8..4a8138b 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,31 @@ +### Functions + +Form-level properties & methods + +```ts +value: V; +isDirty(): boolean; +isInvalid(): boolean; +isTouched(): boolean; +setValue(value: V | ((val: V) => V), options?: SetValueOptions): void; +unsetDirty(): void; +unsetTouched(): void; +``` + +Field-level properties & methods + +```ts +getFieldValue

>(fieldPath: P): FieldValue; +isFieldDirty

>(fieldPath: P): boolean; +isFieldInvalid

>(fieldPath: P): boolean; +isFieldTouched

>(fieldPath: P): boolean; +setFieldDirty

>(fieldPath: P, options?: SetDirtyOptions): void; +setFieldTouched

>(fieldPath: P, options?: SetTouchedOptions): void; +setFieldValue

>(fieldPath: P, value: | FieldValue | ((val: FieldValue) => FieldValue), options?: SetValueOptions): void; +unsetFieldDirty

>(fieldPath: P, options?: UnsetDirtyOptions): void; +unsetFieldTouched

>(fieldPath: P, options?: UnsetTouchedOptions): void; +``` + ### Type-safe transforms ```tsx diff --git a/src/components/field.component.tsx b/src/components/field.component.tsx index 48ffd7f..ff5cef8 100644 --- a/src/components/field.component.tsx +++ b/src/components/field.component.tsx @@ -11,11 +11,11 @@ import type { NestedFormValue, } from '../types'; import { - getError, - getValue, - isDirty, - isInvalid, - isTouched, + getFieldError, + getFieldValue, + isFieldDirty, + isFieldInvalid, + isFieldTouched, } from '../methods'; /** @@ -77,22 +77,22 @@ export function Field< const transform = props.transform; const fieldState = { - error: createMemo(() => getError(formState, fieldPath)), - isDirty: createMemo(() => isDirty(formState, fieldPath)), - isInvalid: createMemo(() => isInvalid(formState, fieldPath)), - isTouched: createMemo(() => isTouched(formState, fieldPath)), - value: createMemo(() => getValue(formState, fieldPath)), + error: createMemo(() => getFieldError(formState, fieldPath)), + isDirty: createMemo(() => isFieldDirty(formState, fieldPath)), + isInvalid: createMemo(() => isFieldInvalid(formState, fieldPath)), + isTouched: createMemo(() => isFieldTouched(formState, fieldPath)), + value: createMemo(() => getFieldValue(formState, fieldPath)), }; const fieldProps = createMemo(() => { - const value = getValue(formState, fieldPath); + const value = getFieldValue(formState, fieldPath); // Transform incoming value if there is a transform function. const incomingValue = transform?.in ? transform.in(value) : value; return { value: incomingValue as T, - onBlur: () => formState.setTouched(fieldPath), + onBlur: () => formState.setFieldTouched(fieldPath), onChange: (eventOrValue: ChangeEvent | T) => { const value = getChangeValue(eventOrValue); @@ -101,8 +101,8 @@ export function Field< ? transform.out(value) : (value as FieldValue); - formState.setValue(fieldPath, outgoingValue); - formState.setDirty(fieldPath); + formState.setFieldValue(fieldPath, outgoingValue); + formState.setFieldDirty(fieldPath); }, }; }); diff --git a/src/methods/get-error.ts b/src/methods/get-field-error.ts similarity index 72% rename from src/methods/get-error.ts rename to src/methods/get-field-error.ts index 7a5653f..2fcd323 100644 --- a/src/methods/get-error.ts +++ b/src/methods/get-field-error.ts @@ -3,7 +3,7 @@ import type { FormValue, FormState, FieldPath } from '../types'; /** * Get error for a field (if there is one). */ -export function getError>( +export function getFieldError>( formState: FormState, fieldPath: P, ): string | null { diff --git a/src/methods/get-value.ts b/src/methods/get-field-value.ts similarity index 74% rename from src/methods/get-value.ts rename to src/methods/get-field-value.ts index b1e6640..2961131 100644 --- a/src/methods/get-value.ts +++ b/src/methods/get-field-value.ts @@ -11,10 +11,10 @@ import type { * @example * ```typescript * const form = createForm({ name: { first: 'bob' } }); - * form.getValue(form, 'name.first') // 'bob' + * form.getFieldValue(form, 'name.first') // 'bob' * ``` */ -export function getValue>( +export function getFieldValue>( formState: FormState, fieldPath: P, ): FieldValue { @@ -23,6 +23,6 @@ export function getValue>( .split('.') .reduce( (state, key) => (state as any)?.[key], - formState.formValue, + formState.value, ) as FieldValue; } diff --git a/src/methods/index.ts b/src/methods/index.ts index 4658713..1c3a59b 100644 --- a/src/methods/index.ts +++ b/src/methods/index.ts @@ -1,10 +1,16 @@ -export * from './get-error'; -export * from './get-value'; +export * from './get-field-error'; +export * from './get-field-value'; export * from './is-dirty'; +export * from './is-field-dirty'; +export * from './is-field-invalid'; +export * from './is-field-touched'; export * from './is-invalid'; export * from './is-touched'; -export * from './set-dirty'; -export * from './set-touched'; +export * from './set-field-dirty'; +export * from './set-field-touched'; +export * from './set-field-value'; export * from './set-value'; export * from './unset-dirty'; +export * from './unset-field-dirty'; +export * from './unset-field-touched'; export * from './unset-touched'; diff --git a/src/methods/is-dirty.ts b/src/methods/is-dirty.ts index bfcc0cb..63b9657 100644 --- a/src/methods/is-dirty.ts +++ b/src/methods/is-dirty.ts @@ -1,40 +1,15 @@ -import type { FieldPath, FormState, FormValue } from '../types'; -import { isTraversable } from '../utils'; +import type { FormState, FormValue } from '../types'; /** - * Whether the form or form field is dirty. + * Whether the form is dirty. * - * A form or form field is dirty if: - * * The direct field path has been modified. - * * Any of its descendant field paths have been modified. + * A form is dirty if any of its field paths are dirty. * * @param formState Form state. - * @param fieldPath (optional) If provided, checks whether the field is dirty; - * otherwise checks the form as a whole. */ -export function isDirty>( +export function isDirty( formState: FormState, - fieldPath?: P, ): boolean { - const { formValue } = formState; const { dirtyFieldPaths } = formState.__internal.fieldStates; - - if (!fieldPath) { - return dirtyFieldPaths.size > 0; - } - - if (dirtyFieldPaths.has(fieldPath)) { - return true; - } - - // No need to check descendants if the value is not an object or array. - if (isTraversable(formValue)) { - for (const dirtyFieldPath of dirtyFieldPaths) { - if (dirtyFieldPath.startsWith(fieldPath)) { - return true; - } - } - } - - return false; + return dirtyFieldPaths.size > 0; } diff --git a/src/methods/is-field-dirty.ts b/src/methods/is-field-dirty.ts new file mode 100644 index 0000000..63a54cc --- /dev/null +++ b/src/methods/is-field-dirty.ts @@ -0,0 +1,35 @@ +import type { FieldPath, FormState, FormValue } from '../types'; +import { isTraversable } from '../utils'; + +/** + * Whether the form field is dirty. + * + * A form field is dirty if: + * * The direct field path has been modified. + * * Any of its descendant field paths have been modified. + * + * @param formState Form state. + * @param fieldPath The field to check for whether it's dirty. + */ +export function isFieldDirty>( + formState: FormState, + fieldPath: P, +): boolean { + const { value: formValue } = formState; + const { dirtyFieldPaths } = formState.__internal.fieldStates; + + if (dirtyFieldPaths.has(fieldPath)) { + return true; + } + + // No need to check descendants if the value is not an object or array. + if (isTraversable(formValue)) { + for (const dirtyFieldPath of dirtyFieldPaths) { + if (dirtyFieldPath.startsWith(fieldPath)) { + return true; + } + } + } + + return false; +} diff --git a/src/methods/is-field-invalid.ts b/src/methods/is-field-invalid.ts new file mode 100644 index 0000000..6d924f6 --- /dev/null +++ b/src/methods/is-field-invalid.ts @@ -0,0 +1,35 @@ +import type { FieldPath, FormState, FormValue } from '../types'; +import { isTraversable } from '../utils'; + +/** + * Whether the form field is invalid. + * + * A form field is invalid if: + * * The direct field path is invalid. + * * Any of its descendant field paths are invalid. + * + * @param formState Form state. + * @param fieldPath The field to check for whether it's invalid. + */ +export function isFieldInvalid< + V extends FormValue, + P extends FieldPath, +>(formState: FormState, fieldPath: P): boolean { + const { value: formValue } = formState; + const { invalidFieldPaths } = formState.__internal.fieldStates; + + if (invalidFieldPaths.has(fieldPath)) { + return true; + } + + // No need to check descendants if the value is not an object or array. + if (isTraversable(formValue)) { + for (const invalidFieldPath of invalidFieldPaths) { + if (invalidFieldPath.startsWith(fieldPath)) { + return true; + } + } + } + + return false; +} diff --git a/src/methods/is-field-touched.ts b/src/methods/is-field-touched.ts new file mode 100644 index 0000000..1700d93 --- /dev/null +++ b/src/methods/is-field-touched.ts @@ -0,0 +1,35 @@ +import type { FormValue, FormState, FieldPath } from '../types'; +import { isTraversable } from '../utils'; + +/** + * Whether the form field is touched. + * + * A form field is touched if: + * * The direct field path has been touched. + * * Any of its descendant field paths have been touched. + * + * @param formState Form state. + * @param fieldPath The field to check for whether it's touched. + */ +export function isFieldTouched< + V extends FormValue, + P extends FieldPath, +>(formState: FormState, fieldPath: P): boolean { + const { value: formValue } = formState; + const { touchedFieldPaths } = formState.__internal.fieldStates; + + if (touchedFieldPaths.has(fieldPath)) { + return true; + } + + // No need to check descendants if the value is not an object or array. + if (isTraversable(formValue)) { + for (const touchedFieldPath of touchedFieldPaths) { + if (touchedFieldPath.startsWith(fieldPath)) { + return true; + } + } + } + + return false; +} diff --git a/src/methods/is-invalid.ts b/src/methods/is-invalid.ts index 65beae1..ade97fd 100644 --- a/src/methods/is-invalid.ts +++ b/src/methods/is-invalid.ts @@ -1,40 +1,15 @@ -import type { FieldPath, FormState, FormValue } from '../types'; -import { isTraversable } from '../utils'; +import type { FormState, FormValue } from '../types'; /** - * Whether the form or form field is invalid. + * Whether the form is invalid. * - * A form or form field is invalid if: - * * The direct field path is invalid. - * * Any of its descendant field paths are invalid. + * A form is invalid if any of its field paths are invalid. * * @param formState Form state. - * @param fieldPath (optional) If provided, checks whether the field is invalid; - * otherwise checks the form as a whole. */ -export function isInvalid>( +export function isInvalid( formState: FormState, - fieldPath?: P, ): boolean { - const { formValue } = formState; const { invalidFieldPaths } = formState.__internal.fieldStates; - - if (!fieldPath) { - return invalidFieldPaths.size > 0; - } - - if (invalidFieldPaths.has(fieldPath)) { - return true; - } - - // No need to check descendants if the value is not an object or array. - if (isTraversable(formValue)) { - for (const invalidFieldPath of invalidFieldPaths) { - if (invalidFieldPath.startsWith(fieldPath)) { - return true; - } - } - } - - return false; + return invalidFieldPaths.size > 0; } diff --git a/src/methods/is-touched.ts b/src/methods/is-touched.ts index 5fc2143..cafd4a8 100644 --- a/src/methods/is-touched.ts +++ b/src/methods/is-touched.ts @@ -1,40 +1,15 @@ -import type { FormValue, FormState, FieldPath } from '../types'; -import { isTraversable } from '../utils'; +import type { FormState, FormValue } from '../types'; /** - * Whether the form or form field is touched. + * Whether the form is touched. * - * A form or form field is touched if: - * * The direct field path has been touched. - * * Any of its descendant field paths have been touched. + * A form is touched if any of its field paths are touched. * * @param formState Form state. - * @param fieldPath (optional) If provided, checks whether the field is touched; - * otherwise checks the form as a whole. */ -export function isTouched>( +export function isTouched( formState: FormState, - fieldPath?: P, ): boolean { - const { formValue } = formState; const { touchedFieldPaths } = formState.__internal.fieldStates; - - if (!fieldPath) { - return touchedFieldPaths.size > 0; - } - - if (touchedFieldPaths.has(fieldPath)) { - return true; - } - - // No need to check descendants if the value is not an object or array. - if (isTraversable(formValue)) { - for (const touchedFieldPath of touchedFieldPaths) { - if (touchedFieldPath.startsWith(fieldPath)) { - return true; - } - } - } - - return false; + return touchedFieldPaths.size > 0; } diff --git a/src/methods/set-dirty.ts b/src/methods/set-field-dirty.ts similarity index 74% rename from src/methods/set-dirty.ts rename to src/methods/set-field-dirty.ts index a4eabb9..efc6020 100644 --- a/src/methods/set-dirty.ts +++ b/src/methods/set-field-dirty.ts @@ -1,17 +1,17 @@ import type { FormValue, FieldPath, FormState } from '../types'; import { isTraversable, getDescendantFieldPaths } from '../utils'; -export interface SetDirtyOptions { +export interface SetFieldDirtyOpts { /** Whether to set all descendant paths as dirty. Defaults to `false`. */ deep?: boolean; } -export function setDirty>( +export function setFieldDirty>( formState: FormState, fieldPath: P, - options?: SetDirtyOptions, + options?: SetFieldDirtyOpts, ) { - const { formValue } = formState; + const { value: formValue } = formState; const { dirtyFieldPaths } = formState.__internal.fieldStates; dirtyFieldPaths.add(fieldPath); diff --git a/src/methods/set-touched.ts b/src/methods/set-field-touched.ts similarity index 68% rename from src/methods/set-touched.ts rename to src/methods/set-field-touched.ts index 8e870a4..4a1e60b 100644 --- a/src/methods/set-touched.ts +++ b/src/methods/set-field-touched.ts @@ -1,17 +1,16 @@ import type { FormValue, FieldPath, FormState } from '../types'; import { getDescendantFieldPaths, isTraversable } from '../utils'; -export interface SetTouchedOptions { +export interface SetFieldTouchedOpts { /** Whether to set all descendant paths as touched. Defaults to `false`. */ deep?: boolean; } -export function setTouched>( - formState: FormState, - fieldPath: P, - options?: SetTouchedOptions, -) { - const { formValue } = formState; +export function setFieldTouched< + V extends FormValue, + P extends FieldPath, +>(formState: FormState, fieldPath: P, options?: SetFieldTouchedOpts) { + const { value: formValue } = formState; const { touchedFieldPaths } = formState.__internal.fieldStates; touchedFieldPaths.add(fieldPath); diff --git a/src/methods/set-field-value.ts b/src/methods/set-field-value.ts new file mode 100644 index 0000000..d3105f0 --- /dev/null +++ b/src/methods/set-field-value.ts @@ -0,0 +1,68 @@ +import { batch } from 'solid-js'; + +import type { + FormValue, + FieldPath, + FormState, + FieldValue, +} from '../types'; + +export interface SetFieldValueOpts { + /** Deeply updates touched state of any modified field paths. */ + setTouched?: boolean; + + /** Deeply updates dirty state of any modified field paths. */ + setDirty?: boolean; +} + +/** + * Set value of field by its path. + */ +export function setFieldValue>( + formState: FormState, + fieldPath: P, + value: FieldValue | ((val: FieldValue) => FieldValue), + options?: SetFieldValueOpts, +): void { + const currentValue = formState.getFieldValue(fieldPath); + + const newValue = + typeof value === 'function' ? value(currentValue) : value; + + batch(() => { + formState.__internal.setFormValue( + updatedObject(formState.value, fieldPath, newValue), + ); + + if (fieldPath && options?.setTouched === true) { + formState.setFieldTouched(fieldPath); + } + + if (fieldPath && options?.setDirty !== false) { + formState.setFieldDirty(fieldPath); + } + }); +} + +// TODO: Avoid `any` types. +function updatedObject( + object: any, + fieldPath: string | null, + newValue: any, +) { + if (!fieldPath) { + return newValue; + } + + const stack = fieldPath.split('.'); + const newObject = { ...object }; + let target = newObject; + + while (stack.length > 1) { + target = target[stack.shift()!]; + } + + target[stack.shift()!] = newValue; + + return newObject; +} diff --git a/src/methods/set-value.ts b/src/methods/set-value.ts index fa2f6e9..67c047c 100644 --- a/src/methods/set-value.ts +++ b/src/methods/set-value.ts @@ -1,114 +1,24 @@ -import { batch } from 'solid-js'; +import type { FormValue, FormState } from '../types'; -import type { - FormValue, - FieldPath, - FormState, - FieldValue, -} from '../types'; - -export interface SetValueOptions { - /** Deeply updates touched state of any modified field paths. */ +export interface SetValueOpts { + /** Deeply updates touched state of any modified paths. */ setTouched?: boolean; - /** Deeply updates dirty state of any modified field paths. */ + /** Deeply updates dirty state of any modified paths. */ setDirty?: boolean; } /** - * Set value of form or field by its path. + * Set value of form. */ - -// Function signature for setting value for form as a whole. export function setValue( formState: FormState, value: V | ((val: V) => V), - options?: SetValueOptions, -): void; - -// Function signature for setting value for form field. -export function setValue>( - formState: FormState, - fieldPath: P, - value: FieldValue | ((val: FieldValue) => FieldValue), - options?: SetValueOptions, -): void; - -// Merged function signatures. -export function setValue>( - formState: FormState, - ...args: unknown[] -) { - const { fieldPath, value, options } = extractArgs( - formState, - ...args, - ); - - const currentValue = fieldPath - ? formState.getValue(fieldPath) - : formState.formValue; - - // TODO: Avoid type coercion. + _options?: SetValueOpts, +): void { const newValue = - typeof value === 'function' ? value(currentValue as any) : value; - - batch(() => { - formState.__internal.setFormValue( - updatedObject(formState.formValue, fieldPath, newValue), - ); - - // TODO: Handle `setTouched` for root form. - if (fieldPath && options?.setTouched === true) { - formState.setTouched(fieldPath); - } - - // TODO: Handle `setDirty` for root form. - if (fieldPath && options?.setDirty !== false) { - formState.setDirty(fieldPath); - } - }); -} - -function extractArgs>( - _: FormState, - ...args: unknown[] -) { - if (typeof args[0] === 'string') { - return { - fieldPath: args[0] as P, - value: args[1] as - | FieldValue - | ((val: FieldValue) => FieldValue), - options: args[2] as SetValueOptions, - }; - } - - return { - fieldPath: null, - value: args[0] as V | ((val: V) => V), - options: args[1] as SetValueOptions, - }; -} - -// TODO: Avoid `any` types. -function updatedObject( - object: any, - fieldPath: string | null, - newValue: any, -) { - if (!fieldPath) { - return newValue; - } - - const stack = fieldPath.split('.'); - const newObject = { ...object }; - let target = newObject; - - while (stack.length > 1) { - target = target[stack.shift()!]; - } - - target[stack.shift()!] = newValue; + typeof value === 'function' ? value(formState.value) : value; - return newObject; + // TODO: Handle updating touched and dirty states. + formState.__internal.setFormValue(newValue); } diff --git a/src/methods/unset-dirty.ts b/src/methods/unset-dirty.ts index 3707e29..14a354c 100644 --- a/src/methods/unset-dirty.ts +++ b/src/methods/unset-dirty.ts @@ -1,28 +1,6 @@ -import type { FormValue, FieldPath, FormState } from '../types'; -import { isTraversable } from '../utils'; +import type { FormValue, FormState } from '../types'; -export interface UnsetDirtyOptions { - /** Whether to unset all descendant paths as dirty. Defaults to `false`. */ - deep?: boolean; -} - -export function unsetDirty>( - formState: FormState, - fieldPath: P, - options?: UnsetDirtyOptions, -) { - const { formValue } = formState; +export function unsetDirty(formState: FormState) { const { dirtyFieldPaths } = formState.__internal.fieldStates; - - dirtyFieldPaths.delete(fieldPath); - - if (options?.deep && isTraversable(formValue)) { - const descendantPaths = Array.from(dirtyFieldPaths.keys()).filter( - key => key.startsWith(fieldPath), - ); - - for (const descendantPath of descendantPaths) { - dirtyFieldPaths.delete(descendantPath); - } - } + dirtyFieldPaths.clear(); } diff --git a/src/methods/unset-field-dirty.ts b/src/methods/unset-field-dirty.ts new file mode 100644 index 0000000..2a613be --- /dev/null +++ b/src/methods/unset-field-dirty.ts @@ -0,0 +1,27 @@ +import type { FormValue, FieldPath, FormState } from '../types'; +import { isTraversable } from '../utils'; + +export interface UnsetFieldDirtyOpts { + /** Whether to unset all descendant paths as dirty. Defaults to `false`. */ + deep?: boolean; +} + +export function unsetFieldDirty< + V extends FormValue, + P extends FieldPath, +>(formState: FormState, fieldPath: P, options?: UnsetFieldDirtyOpts) { + const { value: formValue } = formState; + const { dirtyFieldPaths } = formState.__internal.fieldStates; + + dirtyFieldPaths.delete(fieldPath); + + if (options?.deep && isTraversable(formValue)) { + const descendantPaths = Array.from(dirtyFieldPaths.keys()).filter( + key => key.startsWith(fieldPath), + ); + + for (const descendantPath of descendantPaths) { + dirtyFieldPaths.delete(descendantPath); + } + } +} diff --git a/src/methods/unset-field-touched.ts b/src/methods/unset-field-touched.ts new file mode 100644 index 0000000..3b46297 --- /dev/null +++ b/src/methods/unset-field-touched.ts @@ -0,0 +1,27 @@ +import type { FormValue, FieldPath, FormState } from '../types'; +import { isTraversable } from '../utils'; + +export interface UnsetFieldTouchedOpts { + /** Whether to unset all descendant paths as touched. Defaults to `false`. */ + deep?: boolean; +} + +export function unsetFieldTouched< + V extends FormValue, + P extends FieldPath, +>(formState: FormState, fieldPath: P, options?: UnsetFieldTouchedOpts) { + const { value: formValue } = formState; + const { touchedFieldPaths } = formState.__internal.fieldStates; + + touchedFieldPaths.delete(fieldPath); + + if (options?.deep && isTraversable(formValue)) { + const descendantPaths = Array.from(touchedFieldPaths.keys()).filter( + key => key.startsWith(fieldPath), + ); + + for (const descendantPath of descendantPaths) { + touchedFieldPaths.delete(descendantPath); + } + } +} diff --git a/src/methods/unset-touched.ts b/src/methods/unset-touched.ts index 5383b81..59ade60 100644 --- a/src/methods/unset-touched.ts +++ b/src/methods/unset-touched.ts @@ -1,28 +1,8 @@ -import type { FormValue, FieldPath, FormState } from '../types'; -import { isTraversable } from '../utils'; +import type { FormValue, FormState } from '../types'; -export interface UnsetTouchedOptions { - /** Whether to unset all descendant paths as touched. Defaults to `false`. */ - deep?: boolean; -} - -export function unsetTouched>( +export function unsetTouched( formState: FormState, - fieldPath: P, - options?: UnsetTouchedOptions, ) { - const { formValue } = formState; const { touchedFieldPaths } = formState.__internal.fieldStates; - - touchedFieldPaths.delete(fieldPath); - - if (options?.deep && isTraversable(formValue)) { - const descendantPaths = Array.from(touchedFieldPaths.keys()).filter( - key => key.startsWith(fieldPath), - ); - - for (const descendantPath of descendantPaths) { - touchedFieldPaths.delete(descendantPath); - } - } + touchedFieldPaths.clear(); } diff --git a/src/primitives/create-form.ts b/src/primitives/create-form.ts index 0eaec36..6fb1864 100644 --- a/src/primitives/create-form.ts +++ b/src/primitives/create-form.ts @@ -4,13 +4,19 @@ import { createSignal } from 'solid-js'; import type { FormValue, FormState, FieldStates } from '../types'; import { + isFieldDirty, + isFieldInvalid, + isFieldTouched, + setFieldDirty, + setFieldValue, + setFieldTouched, + getFieldValue, + unsetFieldDirty, + unsetFieldTouched, isDirty, - isInvalid, isTouched, - setDirty, + isInvalid, setValue, - setTouched, - getValue, unsetDirty, unsetTouched, } from '../methods'; @@ -27,30 +33,24 @@ export function createForm( }); const formState: FormState = { - get formValue() { + get value() { return formValue(); }, + getFieldValue: (...args) => getFieldValue(formState, ...args), isDirty: (...args) => isDirty(formState, ...args), + isFieldDirty: (...args) => isFieldDirty(formState, ...args), + isFieldInvalid: (...args) => isFieldInvalid(formState, ...args), + isFieldTouched: (...args) => isFieldTouched(formState, ...args), isInvalid: (...args) => isInvalid(formState, ...args), isTouched: (...args) => isTouched(formState, ...args), - getValue: (...args: unknown[]) => - getValue( - // Coercion is needed because overloaded fn type isn't correctly inferred. - ...([formState, ...args] as unknown as Parameters< - typeof getValue - >), - ), - setDirty: (...args) => setDirty(formState, ...args), - setTouched: (...args) => setTouched(formState, ...args), - setValue: (...args: unknown[]) => - setValue( - // Coercion is needed because overloaded fn type isn't correctly inferred. - ...([formState, ...args] as unknown as Parameters< - typeof setValue - >), - ), + setFieldDirty: (...args) => setFieldDirty(formState, ...args), + setFieldTouched: (...args) => setFieldTouched(formState, ...args), + setFieldValue: (...args) => setFieldValue(formState, ...args), + setValue: (...args) => setValue(formState, ...args), unsetDirty: (...args) => unsetDirty(formState, ...args), + unsetFieldDirty: (...args) => unsetFieldDirty(formState, ...args), unsetTouched: (...args) => unsetTouched(formState, ...args), + unsetFieldTouched: (...args) => unsetFieldTouched(formState, ...args), __internal: { fieldStates, setFormValue, diff --git a/src/types/form-state.model.ts b/src/types/form-state.model.ts index 1348180..7d6c1a3 100644 --- a/src/types/form-state.model.ts +++ b/src/types/form-state.model.ts @@ -3,43 +3,49 @@ import type { FieldStates } from './field-states.model'; import type { FieldValue } from './field-value.model'; import type { FormValue } from './form-value.model'; import type { - SetDirtyOptions, - SetTouchedOptions, - SetValueOptions, - UnsetDirtyOptions, - UnsetTouchedOptions, + SetFieldDirtyOpts, + SetFieldTouchedOpts, + SetFieldValueOpts, + SetValueOpts, + UnsetFieldDirtyOpts, + UnsetFieldTouchedOpts, } from '../methods'; export interface FormState { - formValue: V; - getValue

>(fieldPath: P): FieldValue; - isDirty

>(fieldPath?: P): boolean; - isInvalid

>(fieldPath?: P): boolean; - isTouched

>(fieldPath?: P): boolean; - setDirty

>( + value: V; + getFieldValue

>(fieldPath: P): FieldValue; + isDirty(): boolean; + isFieldDirty

>(fieldPath: P): boolean; + isFieldInvalid

>(fieldPath: P): boolean; + isFieldTouched

>(fieldPath: P): boolean; + isInvalid(): boolean; + isTouched(): boolean; + setFieldDirty

>( fieldPath: P, - options?: SetDirtyOptions, + options?: SetFieldDirtyOpts, ): void; - setTouched

>( + setFieldTouched

>( fieldPath: P, - options?: SetTouchedOptions, + options?: SetFieldTouchedOpts, ): void; - setValue(value: V | ((val: V) => V), options?: SetValueOptions): void; - setValue

>( + setFieldValue

>( fieldPath: P, value: | FieldValue | ((val: FieldValue) => FieldValue), - options?: SetValueOptions, + options?: SetFieldValueOpts, ): void; - unsetDirty

>( + setValue(value: V | ((val: V) => V), options?: SetValueOpts): void; + unsetDirty(): void; + unsetFieldDirty

>( fieldPath: P, - options?: UnsetDirtyOptions, + options?: UnsetFieldDirtyOpts, ): void; - unsetTouched

>( + unsetFieldTouched

>( fieldPath: P, - options?: UnsetTouchedOptions, + options?: UnsetFieldTouchedOpts, ): void; + unsetTouched(): void; __internal: { /**