@codeleap/form 7.3.0-next.0 → 7.3.1-next.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -22,7 +22,7 @@ import { FileField } from "./file";
22
22
  export declare const fields: {
23
23
  text: <A extends {
24
24
  name?: string;
25
- defaultValue?: string | null | undefined;
25
+ defaultValue?: string | (() => string) | (() => Promise<string>) | null | undefined;
26
26
  state?: import("nanostores").WritableAtom<string> | undefined;
27
27
  validate?: import("./text").TextValidator<any, any> | undefined;
28
28
  loader?: ((form: any) => Partial<Omit<import("..").FieldOptions<string, import("./text").TextValidator<any, any>>, "loader">>) | undefined;
@@ -34,7 +34,7 @@ export declare const fields: {
34
34
  list: typeof listFieldFactory;
35
35
  number: <A extends {
36
36
  name?: string;
37
- defaultValue?: number | number[] | null | undefined;
37
+ defaultValue?: number | number[] | (() => number | number[]) | (() => Promise<number | number[]>) | null | undefined;
38
38
  state?: import("..").FieldState<number | number[]> | undefined;
39
39
  validate?: import("./number").NumberValidator<any, any> | undefined;
40
40
  loader?: ((form: any) => Partial<Omit<import("..").FieldOptions<number | number[], import("./number").NumberValidator<any, any>>, "loader">>) | undefined;
@@ -45,7 +45,7 @@ export declare const fields: {
45
45
  }>(options?: A | undefined) => import("..").Field<number | number[], A["validate"], A["validate"] extends infer T ? T extends A["validate"] ? T extends import("..").Validator<number | number[], infer R, any> ? R : never : never : never, A["validate"] extends infer T_1 ? T_1 extends A["validate"] ? T_1 extends import("..").Validator<number | number[], any, infer E> ? E : never : never : never>;
46
46
  boolean: <A extends {
47
47
  name?: string;
48
- defaultValue?: boolean | null | undefined;
48
+ defaultValue?: boolean | (() => boolean) | (() => Promise<boolean>) | null | undefined;
49
49
  state?: import("nanostores").WritableAtom<boolean> | undefined;
50
50
  validate?: import("./bool").BooleanValidator<any, any> | undefined;
51
51
  loader?: ((form: any) => Partial<Omit<import("..").FieldOptions<boolean, import("./bool").BooleanValidator<any, any>>, "loader">>) | undefined;
@@ -53,7 +53,7 @@ export declare const fields: {
53
53
  } & import("..").ExtraFieldOptions>(options?: A | undefined) => import("..").Field<boolean, A["validate"], A["validate"] extends infer T ? T extends A["validate"] ? T extends import("..").Validator<boolean, infer R, any> ? R : never : never : never, A["validate"] extends infer T_1 ? T_1 extends A["validate"] ? T_1 extends import("..").Validator<boolean, any, infer E> ? E : never : never : never>;
54
54
  selectable: <A extends {
55
55
  name?: string;
56
- defaultValue?: (string | number) | null | undefined;
56
+ defaultValue?: (string | number) | (() => string | number) | (() => Promise<string | number>) | null | undefined;
57
57
  state?: import("nanostores").WritableAtom<string | number> | undefined;
58
58
  validate?: import("./selectable").SelectableValidator<string | number, any, any> | undefined;
59
59
  loader?: ((form: any) => Partial<Omit<import("..").FieldOptions<string | number, import("./selectable").SelectableValidator<string | number, any, any>>, "loader">>) | undefined;
@@ -65,7 +65,7 @@ export declare const fields: {
65
65
  }>(options?: A | undefined) => import("..").Field<string | number, A["validate"], A["validate"] extends infer T ? T extends A["validate"] ? T extends import("..").Validator<string | number, infer R, any> ? R : never : never : never, A["validate"] extends infer T_1 ? T_1 extends A["validate"] ? T_1 extends import("..").Validator<string | number, any, infer E> ? E : never : never : never>;
66
66
  date: <A extends {
67
67
  name?: string;
68
- defaultValue?: Date | null | undefined;
68
+ defaultValue?: Date | (() => Date) | (() => Promise<Date>) | null | undefined;
69
69
  state?: import("..").FieldState<Date> | undefined;
70
70
  validate?: import("./date").DateValidator<any, any> | undefined;
71
71
  loader?: ((form: any) => Partial<Omit<import("..").FieldOptions<Date, import("./date").DateValidator<any, any>>, "loader">>) | undefined;
@@ -1,10 +1,10 @@
1
- import { Field } from "../lib/Field";
2
- import { FieldOptions, Validator } from "../types";
1
+ import { Field } from '../lib/Field';
2
+ import { FieldOptions, Validator } from '../types';
3
3
  /** Validator signature for array-valued fields. `R` and `Err` are the result and error types for the whole list, not individual items. */
4
4
  export type ListValidator<Val, R = any, Err = any> = Validator<Val[], R, Err>;
5
5
  type ExtractFieldValue<T> = T extends Field<infer Value, any> ? Value : never;
6
- type AsListValidator<T> = T extends Field<infer Value, infer Validate> ? Validator<Value[], Validate extends Validator<any, infer R, any> ? R[] : never, Validate extends Validator<any, any, infer Err> ? Err[] : never> : never;
7
- export type _ListFieldOptions<T> = T extends Field<infer Value, infer Validate> ? FieldOptions<Value[], Validator<Value[], Validate extends Validator<any, infer R, any> ? R[] : never, Validate extends Validator<any, any, infer Err> ? Err[] : never>> : never;
6
+ type AsListValidator<T> = T extends Field<infer Value, infer Validate> ? Validator<Value[], Validate extends Validator<any, infer R, any> ? R[] : unknown, Validate extends Validator<any, any, infer Err> ? Err[] : unknown> : never;
7
+ export type _ListFieldOptions<T> = T extends Field<infer Value, infer Validate> ? FieldOptions<Value[], Validator<Value[], Validate extends Validator<any, infer R, any> ? R[] : unknown, Validate extends Validator<any, any, infer Err> ? Err[] : unknown>> : never;
8
8
  export type ListFieldOptions<ItemField extends Field<any, any>> = {
9
9
  item: ItemField;
10
10
  } & _ListFieldOptions<ItemField>;
@@ -1 +1 @@
1
- {"version":3,"file":"list.d.ts","sourceRoot":"","sources":["../../src/fields/list.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,cAAc,CAAA;AACpC,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,UAAU,CAAA;AAElD,0IAA0I;AAC1I,MAAM,MAAM,aAAa,CAAC,GAAG,EAAE,CAAC,GAAG,GAAG,EAAE,GAAG,GAAG,GAAG,IAAI,SAAS,CAAC,GAAG,EAAE,EAAE,CAAC,EAAE,GAAG,CAAC,CAAA;AAE7E,KAAK,iBAAiB,CAAC,CAAC,IAAI,CAAC,SAAS,KAAK,CAAC,MAAM,KAAK,EAAE,GAAG,CAAC,GAAG,KAAK,GAAG,KAAK,CAAA;AAE7E,KAAK,eAAe,CAAC,CAAC,IAAI,CAAC,SAAS,KAAK,CAAC,MAAM,KAAK,EAAE,MAAM,QAAQ,CAAC,GACpE,SAAS,CACP,KAAK,EAAE,EACP,QAAQ,SAAS,SAAS,CAAC,GAAG,EAAE,MAAM,CAAC,EAAE,GAAG,CAAC,GAAG,CAAC,EAAE,GAAG,KAAK,EAC3D,QAAQ,SAAS,SAAS,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,GAAG,CAAC,GAAG,GAAG,EAAE,GAAG,KAAK,CAChE,GACA,KAAK,CAAA;AAER,MAAM,MAAM,iBAAiB,CAAC,CAAC,IAAI,CAAC,SAAS,KAAK,CAAC,MAAM,KAAK,EAAE,MAAM,QAAQ,CAAC,GAAG,YAAY,CAC5F,KAAK,EAAE,EACP,SAAS,CACP,KAAK,EAAE,EACP,QAAQ,SAAS,SAAS,CAAC,GAAG,EAAE,MAAM,CAAC,EAAE,GAAG,CAAC,GAAG,CAAC,EAAE,GAAG,KAAK,EAC3D,QAAQ,SAAS,SAAS,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,GAAG,CAAC,GAAG,GAAG,EAAE,GAAG,KAAK,CAChE,CACF,GAAG,KAAK,CAAA;AAET,MAAM,MAAM,gBAAgB,CAAC,SAAS,SAAS,KAAK,CAAC,GAAG,EAAE,GAAG,CAAC,IAAI;IAChE,IAAI,EAAE,SAAS,CAAA;CAChB,GAAI,iBAAiB,CAAC,SAAS,CAAC,CAAA;AAEjC;;;;;;;;;GASG;AACH,qBAAa,SAAS,CAAC,CAAC,SAAS,KAAK,CAAC,GAAG,EAAE,GAAG,CAAC,CAAE,SAAQ,KAAK,CAAC,iBAAiB,CAAC,CAAC,CAAC,EAAE,EAAE,eAAe,CAAC,CAAC,CAAC,CAAC;IACzG,KAAK,SAAS;gBAEF,OAAO,EAAE,gBAAgB,CAAC,CAAC,CAAC;CA+BzC;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,KAAK,CAAC,GAAG,EAAC,GAAG,CAAC,EAAE,OAAO,EAAE,gBAAgB,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,CAErG"}
1
+ {"version":3,"file":"list.d.ts","sourceRoot":"","sources":["../../src/fields/list.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,cAAc,CAAA;AACpC,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,UAAU,CAAA;AAElD,0IAA0I;AAC1I,MAAM,MAAM,aAAa,CAAC,GAAG,EAAE,CAAC,GAAG,GAAG,EAAE,GAAG,GAAG,GAAG,IAAI,SAAS,CAAC,GAAG,EAAE,EAAE,CAAC,EAAE,GAAG,CAAC,CAAA;AAE7E,KAAK,iBAAiB,CAAC,CAAC,IAAI,CAAC,SAAS,KAAK,CAAC,MAAM,KAAK,EAAE,GAAG,CAAC,GAAG,KAAK,GAAG,KAAK,CAAA;AAE7E,KAAK,eAAe,CAAC,CAAC,IAAI,CAAC,SAAS,KAAK,CAAC,MAAM,KAAK,EAAE,MAAM,QAAQ,CAAC,GACpE,SAAS,CACP,KAAK,EAAE,EACP,QAAQ,SAAS,SAAS,CAAC,GAAG,EAAE,MAAM,CAAC,EAAE,GAAG,CAAC,GAAG,CAAC,EAAE,GAAG,OAAO,EAC7D,QAAQ,SAAS,SAAS,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,GAAG,CAAC,GAAG,GAAG,EAAE,GAAG,OAAO,CAClE,GACA,KAAK,CAAA;AAER,MAAM,MAAM,iBAAiB,CAAC,CAAC,IAAI,CAAC,SAAS,KAAK,CAAC,MAAM,KAAK,EAAE,MAAM,QAAQ,CAAC,GAAG,YAAY,CAC5F,KAAK,EAAE,EACP,SAAS,CACP,KAAK,EAAE,EACP,QAAQ,SAAS,SAAS,CAAC,GAAG,EAAE,MAAM,CAAC,EAAE,GAAG,CAAC,GAAG,CAAC,EAAE,GAAG,OAAO,EAC7D,QAAQ,SAAS,SAAS,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,GAAG,CAAC,GAAG,GAAG,EAAE,GAAG,OAAO,CAClE,CACF,GAAG,KAAK,CAAA;AAET,MAAM,MAAM,gBAAgB,CAAC,SAAS,SAAS,KAAK,CAAC,GAAG,EAAE,GAAG,CAAC,IAAI;IAChE,IAAI,EAAE,SAAS,CAAA;CAChB,GAAG,iBAAiB,CAAC,SAAS,CAAC,CAAA;AAEhC;;;;;;;;;GASG;AACH,qBAAa,SAAS,CAAC,CAAC,SAAS,KAAK,CAAC,GAAG,EAAE,GAAG,CAAC,CAAE,SAAQ,KAAK,CAAC,iBAAiB,CAAC,CAAC,CAAC,EAAE,EAAE,eAAe,CAAC,CAAC,CAAC,CAAC;IACzG,KAAK,SAAS;gBAEF,OAAO,EAAE,gBAAgB,CAAC,CAAC,CAAC;CAiCzC;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,KAAK,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE,OAAO,EAAE,gBAAgB,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,CAEtG"}
@@ -1,4 +1,4 @@
1
- import { Field } from "../lib/Field";
1
+ import { Field } from '../lib/Field';
2
2
  /**
3
3
  * Field that holds an array of values, each validated by a delegate `item`
4
4
  * field. The built-in validator iterates every element through
@@ -10,17 +10,18 @@ import { Field } from "../lib/Field";
10
10
  * does not own its own atom. Do not call `use()` on it directly.
11
11
  */
12
12
  export class ListField extends Field {
13
- _type = "LIST";
13
+ _type = 'LIST';
14
14
  constructor(options) {
15
15
  super({
16
16
  ...options,
17
17
  validate: ((v, form) => {
18
- if (!options.item?.validate)
18
+ if (!options.item?.validate) {
19
19
  return {
20
- isValid: true
20
+ isValid: true,
21
21
  };
22
+ }
22
23
  const errors = [];
23
- for (const value of v) {
24
+ for (const value of (v ?? [])) {
24
25
  const validation = options.item?.validate?.(value);
25
26
  if (!validation.isValid) {
26
27
  errors.push(validation);
@@ -1 +1 @@
1
- {"version":3,"file":"list.js","sourceRoot":"","sources":["../../src/fields/list.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,cAAc,CAAA;AA6BpC;;;;;;;;;GASG;AACH,MAAM,OAAO,SAAqC,SAAQ,KAAiD;IACzG,KAAK,GAAG,MAAM,CAAA;IAEd,YAAY,OAA4B;QACtC,KAAK,CAAC;YACJ,GAAG,OAAO;YACV,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE;gBACrB,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,QAAQ;oBAAE,OAAO;wBAClC,OAAO,EAAE,IAAI;qBACd,CAAA;gBAED,MAAM,MAAM,GAAG,EAAE,CAAA;gBAEjB,KAAK,MAAM,KAAK,IAAI,CAAC,EAAE,CAAC;oBACtB,MAAM,UAAU,GAAG,OAAO,CAAC,IAAI,EAAE,QAAQ,EAAE,CAAC,KAAK,CAAC,CAAA;oBAElD,IAAI,CAAC,UAAU,CAAC,OAAO,EAAE,CAAC;wBACxB,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;oBACzB,CAAC;gBACH,CAAC;gBAED,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;oBAClB,OAAO;wBACL,OAAO,EAAE,KAAK;wBACd,KAAK,EAAE,MAAM;qBACd,CAAA;gBACH,CAAC;gBAED,OAAO;oBACL,OAAO,EAAE,IAAI;iBACd,CAAA;YACH,CAAC,CAAuB;SAC8C,CAAC,CAAA;IAC3E,CAAC;CACF;AAED;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAA2B,OAA4B;IACrF,OAAO,IAAI,SAAS,CAAC,OAAO,CAAC,CAAA;AAC/B,CAAC"}
1
+ {"version":3,"file":"list.js","sourceRoot":"","sources":["../../src/fields/list.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,cAAc,CAAA;AA6BpC;;;;;;;;;GASG;AACH,MAAM,OAAO,SAAqC,SAAQ,KAAiD;IACzG,KAAK,GAAG,MAAM,CAAA;IAEd,YAAY,OAA4B;QACtC,KAAK,CAAC;YACJ,GAAG,OAAO;YACV,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE;gBACrB,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,QAAQ,EAAE,CAAC;oBAC5B,OAAO;wBACL,OAAO,EAAE,IAAI;qBACd,CAAA;gBACH,CAAC;gBAED,MAAM,MAAM,GAAG,EAAE,CAAA;gBAEjB,KAAK,MAAM,KAAK,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC;oBAC9B,MAAM,UAAU,GAAG,OAAO,CAAC,IAAI,EAAE,QAAQ,EAAE,CAAC,KAAK,CAAC,CAAA;oBAElD,IAAI,CAAC,UAAU,CAAC,OAAO,EAAE,CAAC;wBACxB,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;oBACzB,CAAC;gBACH,CAAC;gBAED,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;oBAClB,OAAO;wBACL,OAAO,EAAE,KAAK;wBACd,KAAK,EAAE,MAAM;qBACd,CAAA;gBACH,CAAC;gBAED,OAAO;oBACL,OAAO,EAAE,IAAI;iBACd,CAAA;YACH,CAAC,CAAuB;SAC8C,CAAC,CAAA;IAC3E,CAAC;CACF;AAED;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAA4B,OAA4B;IACtF,OAAO,IAAI,SAAS,CAAC,OAAO,CAAC,CAAA;AAC/B,CAAC"}
@@ -1,4 +1,5 @@
1
1
  import { GlobalState } from "@codeleap/store";
2
+ import { DependencyList } from "react";
2
3
  import { FieldPaths, FieldTuples, FormDef, FormValues, PropertyForKeys, ValidationResult } from "../types";
3
4
  type FormSelector<T extends FormDef, S> = (form: Form<T>) => S;
4
5
  /**
@@ -37,9 +38,10 @@ declare class Form<T extends FormDef> {
37
38
  slice<K extends FieldPaths<T>>(field: K): import("nanostores").WritableAtom<unknown>;
38
39
  iterFields<V>(cb: (field: FieldTuples<T>, index: number) => V): V[];
39
40
  /**
40
- * Bulk-sets field values from a partial record. Boolean fields require an
41
- * explicit `boolean` value; all other fields skip `undefined` and falsy
42
- * values. Use `resetValues()` to restore all fields to their initial values.
41
+ * Bulk-sets field values from a partial record. Only keys present in
42
+ * `values` are written absent keys leave the corresponding field
43
+ * untouched. Falsy values (`''`, `0`, `false`, `null`) are written as-is.
44
+ * Use `resetValues()` to restore all fields to their initial values.
43
45
  */
44
46
  setValues(values: Partial<FormValues<T>>): void;
45
47
  resetValues(): void;
@@ -65,6 +67,17 @@ declare class Form<T extends FormDef> {
65
67
  register(field: FieldPaths<T>): import("..").IFieldProps;
66
68
  use<Selected>(selector: FormSelector<T, Selected>): Selected;
67
69
  useReset(): void;
70
+ /**
71
+ * Initializes form fields from async or derived data at mount time.
72
+ * Calls `setValues` inside a `useEffect` whenever `values` is non-null and
73
+ * the deps array changes. Pass `null` or `undefined` to skip (e.g. while a
74
+ * query is still loading).
75
+ *
76
+ * @example
77
+ * const { data, isSuccess } = useQuery(...)
78
+ * myForm.useInitialize(isSuccess ? data : null)
79
+ */
80
+ useInitialize(values: Partial<FormValues<T>> | null | undefined, deps?: DependencyList): void;
68
81
  useShared<Selected>(selector: FormSelector<T, Selected>): Selected;
69
82
  }
70
83
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"Form.d.ts","sourceRoot":"","sources":["../../src/lib/Form.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,WAAW,EAAe,MAAM,iBAAiB,CAAA;AAG5E,OAAO,EAAE,UAAU,EAAuB,WAAW,EAAE,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAA;AAiB/H,KAAK,YAAY,CAAC,CAAC,SAAS,OAAO,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,KAAM,CAAC,CAAA;AAI/D;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,cAAM,IAAI,CAAC,CAAC,SAAS,OAAO;IAC1B,EAAE,EAAE,MAAM,CAAA;IACV,MAAM,EAAE,CAAC,CAAA;IACT,KAAK,EAAE,WAAW,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAA;gBAGrB,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;IAchC,IAAI,MAAM,8HAET;IAED,IAAI,SAAS,YAEZ;IAGD,OAAO;IAQP,IAAI,OAAO,YAIV;IAED,KAAK,CAAC,CAAC,SAAS,UAAU,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC;IAgBvC,UAAU,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,KAAK,EAAE,WAAW,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,CAAC;IAe7D;;;;OAIG;IACH,SAAS,CAAC,MAAM,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;IAQxC,WAAW;IAMX,WAAW;IAUX;;;;OAIG;IACH,YAAY;;;;IAWZ,QAAQ,CAAC,MAAM,SAAS,UAAU,CAAC,CAAC,CAAC,EAAE,GAAG,UAAU,CAAC,CAAC,CAAC,EAAE,EAAE,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,YAAY,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,eAAe,CAAC,CAAC,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,iBAAiB,CAAC;IA2BxK;;;;OAIG;IACH,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC;IAO7B,GAAG,CAAC,QAAQ,EAAE,QAAQ,EAAE,YAAY,CAAC,CAAC,EAAE,QAAQ,CAAC,GAAG,QAAQ;IAQ5D,QAAQ;IAKR,SAAS,CAAC,QAAQ,EAAE,QAAQ,EAAE,YAAY,CAAC,CAAC,EAAE,QAAQ,CAAC,GAAG,QAAQ;CAiBnE;AAGD;;;;;;;;GAQG;AACH,wBAAgB,OAAO,CAAC,CAAC,SAAS,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,WAO9D;AAED;;;;;GAKG;AACH,wBAAgB,IAAI,CAAC,GAAG,SAAS,OAAO,EAAE,GAAG,IAAI,EAAE,qBAAqB,CAAC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,aAEzF"}
1
+ {"version":3,"file":"Form.d.ts","sourceRoot":"","sources":["../../src/lib/Form.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,WAAW,EAAe,MAAM,iBAAiB,CAAA;AAE5E,OAAO,EAAE,cAAc,EAAsE,MAAM,OAAO,CAAA;AAC1G,OAAO,EAAE,UAAU,EAAuB,WAAW,EAAE,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAA;AAiB/H,KAAK,YAAY,CAAC,CAAC,SAAS,OAAO,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,KAAM,CAAC,CAAA;AAI/D;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,cAAM,IAAI,CAAC,CAAC,SAAS,OAAO;IAC1B,EAAE,EAAE,MAAM,CAAA;IACV,MAAM,EAAE,CAAC,CAAA;IACT,KAAK,EAAE,WAAW,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAA;gBAGrB,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;IAehC,IAAI,MAAM,8HAET;IAED,IAAI,SAAS,YAEZ;IAGD,OAAO;IAQP,IAAI,OAAO,YAIV;IAED,KAAK,CAAC,CAAC,SAAS,UAAU,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC;IAgBvC,UAAU,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,KAAK,EAAE,WAAW,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,CAAC;IAe7D;;;;;OAKG;IACH,SAAS,CAAC,MAAM,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;IAQxC,WAAW;IAMX,WAAW;IAUX;;;;OAIG;IACH,YAAY;;;;IAWZ,QAAQ,CAAC,MAAM,SAAS,UAAU,CAAC,CAAC,CAAC,EAAE,GAAG,UAAU,CAAC,CAAC,CAAC,EAAE,EAAE,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,YAAY,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,eAAe,CAAC,CAAC,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,iBAAiB,CAAC;IA2BxK;;;;OAIG;IACH,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC;IAO7B,GAAG,CAAC,QAAQ,EAAE,QAAQ,EAAE,YAAY,CAAC,CAAC,EAAE,QAAQ,CAAC,GAAG,QAAQ;IAQ5D,QAAQ;IAMR;;;;;;;;;OASG;IACH,aAAa,CAAC,MAAM,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,GAAG,SAAS,EAAE,IAAI,CAAC,EAAE,cAAc;IAMtF,SAAS,CAAC,QAAQ,EAAE,QAAQ,EAAE,YAAY,CAAC,CAAC,EAAE,QAAQ,CAAC,GAAG,QAAQ;CAiBnE;AAGD;;;;;;;;GAQG;AACH,wBAAgB,OAAO,CAAC,CAAC,SAAS,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,WAO9D;AAED;;;;;GAKG;AACH,wBAAgB,IAAI,CAAC,GAAG,SAAS,OAAO,EAAE,GAAG,IAAI,EAAE,qBAAqB,CAAC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,aAEzF"}
package/dist/lib/Form.js CHANGED
@@ -45,6 +45,7 @@ class Form {
45
45
  this.use = this.use.bind(this);
46
46
  this.useShared = this.useShared.bind(this);
47
47
  this.useReset = this.useReset.bind(this);
48
+ this.useInitialize = this.useInitialize.bind(this);
48
49
  }
49
50
  get values() {
50
51
  return this.state.get();
@@ -82,17 +83,17 @@ class Form {
82
83
  return results;
83
84
  }
84
85
  /**
85
- * Bulk-sets field values from a partial record. Boolean fields require an
86
- * explicit `boolean` value; all other fields skip `undefined` and falsy
87
- * values. Use `resetValues()` to restore all fields to their initial values.
86
+ * Bulk-sets field values from a partial record. Only keys present in
87
+ * `values` are written absent keys leave the corresponding field
88
+ * untouched. Falsy values (`''`, `0`, `false`, `null`) are written as-is.
89
+ * Use `resetValues()` to restore all fields to their initial values.
88
90
  */
89
91
  setValues(values) {
92
+ const source = values ?? {};
90
93
  this.iterFields(([name, field]) => {
91
- const value = values?.[name];
92
- if (field._type === 'BOOLEAN' && TypeGuards.isBoolean(value))
93
- field.setValue(value);
94
- else if (value)
95
- field.setValue(value);
94
+ if (!(name in source))
95
+ return;
96
+ field.setValue(source[name]);
96
97
  });
97
98
  }
98
99
  resetValues() {
@@ -158,6 +159,22 @@ class Form {
158
159
  this.resetValues();
159
160
  });
160
161
  }
162
+ /**
163
+ * Initializes form fields from async or derived data at mount time.
164
+ * Calls `setValues` inside a `useEffect` whenever `values` is non-null and
165
+ * the deps array changes. Pass `null` or `undefined` to skip (e.g. while a
166
+ * query is still loading).
167
+ *
168
+ * @example
169
+ * const { data, isSuccess } = useQuery(...)
170
+ * myForm.useInitialize(isSuccess ? data : null)
171
+ */
172
+ useInitialize(values, deps) {
173
+ useEffect(() => {
174
+ if (!TypeGuards.isNil(values))
175
+ this.setValues(values);
176
+ }, deps ?? [values]);
177
+ }
161
178
  useShared(selector) {
162
179
  const [selected, setSelected] = useState(() => selector(this));
163
180
  const reselect = useCallback(() => {
@@ -1 +1 @@
1
- {"version":3,"file":"Form.js","sourceRoot":"","sources":["../../src/lib/Form.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Form.js","sourceRoot":"","sources":["../../src/lib/Form.ts"],"names":[],"mappings":"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"}
@@ -5,7 +5,7 @@ export interface ExtraFieldOptions {
5
5
  }
6
6
  export type FieldOptions<T, Validate extends Validator<T, any, any>> = {
7
7
  name?: string;
8
- defaultValue?: T | null;
8
+ defaultValue?: T | null | (() => T) | (() => Promise<T>);
9
9
  state?: FieldState<T>;
10
10
  validate?: Validate;
11
11
  loader?: (form: any) => Partial<Omit<FieldOptions<T, Validate>, 'loader'>>;
@@ -1 +1 @@
1
- {"version":3,"file":"field.d.ts","sourceRoot":"","sources":["../../src/types/field.ts"],"names":[],"mappings":"AAAA,OAAO,EAAG,aAAa,EAAE,MAAM,YAAY,CAAA;AAC3C,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAIxC,MAAM,MAAM,UAAU,CAAC,CAAC,IAAI,aAAa,CAAC,CAAC,CAAC,CAAA;AAE5C,MAAM,WAAW,iBAAiB;CAEjC;AAED,MAAM,MAAM,YAAY,CACtB,CAAC,EACD,QAAQ,SAAS,SAAS,CAAC,CAAC,EAAE,GAAG,EAAE,GAAG,CAAC,IACrC;IACF,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,YAAY,CAAC,EAAE,CAAC,GAAG,IAAI,CAAA;IACvB,KAAK,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC,CAAA;IAErB,QAAQ,CAAC,EAAE,QAAQ,CAAA;IAEnB,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,OAAO,CAC7B,IAAI,CAAC,YAAY,CAAC,CAAC,EAAE,QAAQ,CAAE,EAAE,QAAQ,CAAC,CAC3C,CAAA;IAED,aAAa,CAAC,EAAE,CAAC,QAAQ,EAAE,CAAC,KAAK,IAAI,CAAA;CAEtC,GAAG,iBAAiB,CAAA;AAGrB,MAAM,MAAM,kBAAkB,GAAG;IAC/B,CAAC,CAAC,EAAE,MAAM,CAAA;IACV,CAAC,CAAC,EAAE,MAAM,CAAA;IACV,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,KAAK,CAAC,EAAE,MAAM,CAAA;CACf,CAAA"}
1
+ {"version":3,"file":"field.d.ts","sourceRoot":"","sources":["../../src/types/field.ts"],"names":[],"mappings":"AAAA,OAAO,EAAG,aAAa,EAAE,MAAM,YAAY,CAAA;AAC3C,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAIxC,MAAM,MAAM,UAAU,CAAC,CAAC,IAAI,aAAa,CAAC,CAAC,CAAC,CAAA;AAE5C,MAAM,WAAW,iBAAiB;CAEjC;AAED,MAAM,MAAM,YAAY,CACtB,CAAC,EACD,QAAQ,SAAS,SAAS,CAAC,CAAC,EAAE,GAAG,EAAE,GAAG,CAAC,IACrC;IACF,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,YAAY,CAAC,EAAE,CAAC,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,MAAM,OAAO,CAAC,CAAC,CAAC,CAAC,CAAA;IACxD,KAAK,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC,CAAA;IAErB,QAAQ,CAAC,EAAE,QAAQ,CAAA;IAEnB,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,OAAO,CAC7B,IAAI,CAAC,YAAY,CAAC,CAAC,EAAE,QAAQ,CAAE,EAAE,QAAQ,CAAC,CAC3C,CAAA;IAED,aAAa,CAAC,EAAE,CAAC,QAAQ,EAAE,CAAC,KAAK,IAAI,CAAA;CAEtC,GAAG,iBAAiB,CAAA;AAGrB,MAAM,MAAM,kBAAkB,GAAG;IAC/B,CAAC,CAAC,EAAE,MAAM,CAAA;IACV,CAAC,CAAC,EAAE,MAAM,CAAA;IACV,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,KAAK,CAAC,EAAE,MAAM,CAAA;CACf,CAAA"}
@@ -10,6 +10,8 @@ export interface IFieldRef<T> {
10
10
  emit(event: string, ...args: any[]): void;
11
11
  }
12
12
  export interface IFieldProps {
13
+ name?: string;
14
+ field: any;
13
15
  }
14
16
  export type PropTransformer = (props: AnyRecord) => AnyRecord;
15
17
  //# sourceMappingURL=globals.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"globals.d.ts","sourceRoot":"","sources":["../../src/types/globals.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAE3C,MAAM,WAAW,SAAS,CAAC,CAAC;IAC1B,QAAQ,IAAI,CAAC,CAAA;IACb,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IAC/B,KAAK,IAAI,IAAI,CAAA;IACb,IAAI,IAAI,IAAI,CAAA;IACZ,WAAW,IAAI,IAAI,CAAA;IACnB,qBAAqB,IAAI,IAAI,CAAA;IAC7B,SAAS,IAAI,IAAI,CAAA;IACjB,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI,CAAA;CAC1C;AAGD,MAAM,WAAW,WAAW;CAE3B;AAED,MAAM,MAAM,eAAe,GAAG,CAAC,KAAK,EAAE,SAAS,KAAK,SAAS,CAAA"}
1
+ {"version":3,"file":"globals.d.ts","sourceRoot":"","sources":["../../src/types/globals.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAE3C,MAAM,WAAW,SAAS,CAAC,CAAC;IAC1B,QAAQ,IAAI,CAAC,CAAA;IACb,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IAC/B,KAAK,IAAI,IAAI,CAAA;IACb,IAAI,IAAI,IAAI,CAAA;IACZ,WAAW,IAAI,IAAI,CAAA;IACnB,qBAAqB,IAAI,IAAI,CAAA;IAC7B,SAAS,IAAI,IAAI,CAAA;IACjB,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI,CAAA;CAC1C;AAGD,MAAM,WAAW,WAAW;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,GAAG,CAAA;CACX;AAED,MAAM,MAAM,eAAe,GAAG,CAAC,KAAK,EAAE,SAAS,KAAK,SAAS,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codeleap/form",
3
- "version": "7.3.0-next.0",
3
+ "version": "7.3.1-next.0",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "exports": {
@@ -22,10 +22,10 @@
22
22
  "directory": "packages/form"
23
23
  },
24
24
  "devDependencies": {
25
- "@codeleap/config": "7.3.0-next.0",
26
- "@codeleap/types": "7.3.0-next.0",
27
- "@codeleap/store": "7.3.0-next.0",
28
- "@codeleap/hooks": "7.3.0-next.0",
25
+ "@codeleap/config": "7.3.1-next.0",
26
+ "@codeleap/types": "7.3.1-next.0",
27
+ "@codeleap/store": "7.3.1-next.0",
28
+ "@codeleap/hooks": "7.3.1-next.0",
29
29
  "zod": "4.4.3",
30
30
  "ts-node-dev": "1.1.8"
31
31
  },
@@ -35,10 +35,10 @@
35
35
  "playground": "bun src/test.ts"
36
36
  },
37
37
  "peerDependencies": {
38
- "@codeleap/types": "7.3.0-next.0",
39
- "@codeleap/store": "7.3.0-next.0",
40
- "@codeleap/logger": "7.3.0-next.0",
41
- "@codeleap/hooks": "7.3.0-next.0",
38
+ "@codeleap/types": "7.3.1-next.0",
39
+ "@codeleap/store": "7.3.1-next.0",
40
+ "@codeleap/logger": "7.3.1-next.0",
41
+ "@codeleap/hooks": "7.3.1-next.0",
42
42
  "zod": "*",
43
43
  "react": "19.1.0",
44
44
  "typescript": "5.5.2"
@@ -1,31 +1,31 @@
1
- import { Field } from "../lib/Field"
2
- import { FieldOptions, Validator } from "../types"
1
+ import { Field } from '../lib/Field'
2
+ import { FieldOptions, Validator } from '../types'
3
3
 
4
4
  /** Validator signature for array-valued fields. `R` and `Err` are the result and error types for the whole list, not individual items. */
5
5
  export type ListValidator<Val, R = any, Err = any> = Validator<Val[], R, Err>
6
6
 
7
7
  type ExtractFieldValue<T> = T extends Field<infer Value, any> ? Value : never
8
8
 
9
- type AsListValidator<T> = T extends Field<infer Value, infer Validate> ?
9
+ type AsListValidator<T> = T extends Field<infer Value, infer Validate> ?
10
10
  Validator<
11
- Value[],
12
- Validate extends Validator<any, infer R, any> ? R[] : never,
13
- Validate extends Validator<any, any, infer Err> ? Err[] : never
11
+ Value[],
12
+ Validate extends Validator<any, infer R, any> ? R[] : unknown,
13
+ Validate extends Validator<any, any, infer Err> ? Err[] : unknown
14
14
  >
15
15
  : never
16
16
 
17
17
  export type _ListFieldOptions<T> = T extends Field<infer Value, infer Validate> ? FieldOptions<
18
18
  Value[],
19
19
  Validator<
20
- Value[],
21
- Validate extends Validator<any, infer R, any> ? R[] : never,
22
- Validate extends Validator<any, any, infer Err> ? Err[] : never
20
+ Value[],
21
+ Validate extends Validator<any, infer R, any> ? R[] : unknown,
22
+ Validate extends Validator<any, any, infer Err> ? Err[] : unknown
23
23
  >
24
24
  > : never
25
25
 
26
26
  export type ListFieldOptions<ItemField extends Field<any, any>> = {
27
27
  item: ItemField
28
- } & _ListFieldOptions<ItemField>
28
+ } & _ListFieldOptions<ItemField>
29
29
 
30
30
  /**
31
31
  * Field that holds an array of values, each validated by a delegate `item`
@@ -38,19 +38,21 @@ export type ListFieldOptions<ItemField extends Field<any, any>> = {
38
38
  * does not own its own atom. Do not call `use()` on it directly.
39
39
  */
40
40
  export class ListField<T extends Field<any, any>> extends Field<ExtractFieldValue<T>[], AsListValidator<T>> {
41
- _type = "LIST"
41
+ _type = 'LIST'
42
42
 
43
43
  constructor(options: ListFieldOptions<T>) {
44
44
  super({
45
45
  ...options,
46
46
  validate: ((v, form) => {
47
- if (!options.item?.validate) return {
48
- isValid: true
47
+ if (!options.item?.validate) {
48
+ return {
49
+ isValid: true,
50
+ }
49
51
  }
50
52
 
51
53
  const errors = []
52
54
 
53
- for (const value of v) {
55
+ for (const value of (v ?? [])) {
54
56
  const validation = options.item?.validate?.(value)
55
57
 
56
58
  if (!validation.isValid) {
@@ -80,6 +82,6 @@ export class ListField<T extends Field<any, any>> extends Field<ExtractFieldValu
80
82
  * exists to provide a consistent `fields.list(...)` call signature alongside
81
83
  * the other entries in the `fields` namespace.
82
84
  */
83
- export function listFieldFactory<T extends Field<any,any>>(options: ListFieldOptions<T>): ListField<T> {
85
+ export function listFieldFactory<T extends Field<any, any>>(options: ListFieldOptions<T>): ListField<T> {
84
86
  return new ListField(options)
85
- }
87
+ }
package/src/lib/Form.ts CHANGED
@@ -64,6 +64,7 @@ class Form<T extends FormDef> {
64
64
  this.use = this.use.bind(this)
65
65
  this.useShared = this.useShared.bind(this)
66
66
  this.useReset = this.useReset.bind(this)
67
+ this.useInitialize = this.useInitialize.bind(this)
67
68
  }
68
69
 
69
70
  get values(){
@@ -121,15 +122,16 @@ class Form<T extends FormDef> {
121
122
  }
122
123
 
123
124
  /**
124
- * Bulk-sets field values from a partial record. Boolean fields require an
125
- * explicit `boolean` value; all other fields skip `undefined` and falsy
126
- * values. Use `resetValues()` to restore all fields to their initial values.
125
+ * Bulk-sets field values from a partial record. Only keys present in
126
+ * `values` are written absent keys leave the corresponding field
127
+ * untouched. Falsy values (`''`, `0`, `false`, `null`) are written as-is.
128
+ * Use `resetValues()` to restore all fields to their initial values.
127
129
  */
128
130
  setValues(values: Partial<FormValues<T>>) {
131
+ const source = values ?? {}
129
132
  this.iterFields(([name, field]) => {
130
- const value = values?.[name]
131
- if (field._type === 'BOOLEAN' && TypeGuards.isBoolean(value)) field.setValue(value)
132
- else if (value) field.setValue(value)
133
+ if (!(name in source)) return
134
+ field.setValue(source[name] as any)
133
135
  })
134
136
  }
135
137
 
@@ -217,6 +219,23 @@ class Form<T extends FormDef> {
217
219
  this.resetValues()
218
220
  })
219
221
  }
222
+
223
+ /**
224
+ * Initializes form fields from async or derived data at mount time.
225
+ * Calls `setValues` inside a `useEffect` whenever `values` is non-null and
226
+ * the deps array changes. Pass `null` or `undefined` to skip (e.g. while a
227
+ * query is still loading).
228
+ *
229
+ * @example
230
+ * const { data, isSuccess } = useQuery(...)
231
+ * myForm.useInitialize(isSuccess ? data : null)
232
+ */
233
+ useInitialize(values: Partial<FormValues<T>> | null | undefined, deps?: DependencyList) {
234
+ useEffect(() => {
235
+ if (!TypeGuards.isNil(values)) this.setValues(values)
236
+ }, deps ?? [values])
237
+ }
238
+
220
239
  useShared<Selected>(selector: FormSelector<T, Selected>): Selected {
221
240
  const [selected, setSelected] = useState(() => selector(this))
222
241
 
@@ -0,0 +1,186 @@
1
+ import { describe, it, expect, afterEach } from 'bun:test'
2
+ import { Field, ValidationError } from '../lib/Field'
3
+ import { fields } from '../fields'
4
+ import { z } from 'zod'
5
+ import { zodValidator } from '../validators'
6
+ import './setup'
7
+
8
+ afterEach(() => {
9
+ Field.transformers.clear()
10
+ })
11
+
12
+ describe('Field construction', () => {
13
+ it('initializes with static defaultValue', () => {
14
+ const field = fields.text({ defaultValue: 'hello' })
15
+ expect(field.value).toBe('hello')
16
+ })
17
+
18
+ it('initializes with function defaultValue', () => {
19
+ const field = fields.text({ defaultValue: () => 'from fn' })
20
+ expect(field.value).toBe('from fn')
21
+ })
22
+
23
+ it('initializes with undefined when no defaultValue', () => {
24
+ const field = fields.text({})
25
+ expect(field.value).toBeUndefined()
26
+ })
27
+
28
+ it('errorRevealed starts as false', () => {
29
+ const field = fields.text({ defaultValue: '' })
30
+ expect(field.isErrorRevealed).toBe(false)
31
+ })
32
+ })
33
+
34
+ describe('Field.setValue', () => {
35
+ it('updates the stored value', () => {
36
+ const field = fields.text({ defaultValue: '' })
37
+ field.setValue('updated')
38
+ expect(field.value).toBe('updated')
39
+ })
40
+
41
+ it('calls onValueChange callback', () => {
42
+ const calls: string[] = []
43
+ const field = fields.text({
44
+ defaultValue: '',
45
+ onValueChange: (v) => calls.push(v),
46
+ })
47
+ field.setValue('hello')
48
+ expect(calls).toEqual(['hello'])
49
+ })
50
+ })
51
+
52
+ describe('Field.resetValue', () => {
53
+ it('restores the initial value', () => {
54
+ const field = fields.text({ defaultValue: 'initial' })
55
+ field.setValue('changed')
56
+ field.resetValue()
57
+ expect(field.value).toBe('initial')
58
+ })
59
+
60
+ it('hides the revealed error', () => {
61
+ const field = fields.text({ defaultValue: '' })
62
+ field.revealError()
63
+ field.resetValue()
64
+ expect(field.isErrorRevealed).toBe(false)
65
+ })
66
+ })
67
+
68
+ describe('Field.changed', () => {
69
+ it('returns false when value equals initial', () => {
70
+ const field = fields.text({ defaultValue: 'init' })
71
+ expect(field.changed()).toBe(false)
72
+ })
73
+
74
+ it('returns true after setValue with a different value', () => {
75
+ const field = fields.text({ defaultValue: 'init' })
76
+ field.setValue('different')
77
+ expect(field.changed()).toBe(true)
78
+ })
79
+
80
+ it('returns false after resetValue', () => {
81
+ const field = fields.text({ defaultValue: 'init' })
82
+ field.setValue('different')
83
+ field.resetValue()
84
+ expect(field.changed()).toBe(false)
85
+ })
86
+ })
87
+
88
+ describe('Field.validate', () => {
89
+ it('returns isValid: true for a passing validator', () => {
90
+ const field = fields.text({
91
+ defaultValue: 'hello',
92
+ validate: zodValidator(z.string().min(1)),
93
+ })
94
+ expect(field.validate().isValid).toBe(true)
95
+ })
96
+
97
+ it('returns isValid: false for a failing validator', () => {
98
+ const field = fields.text({
99
+ defaultValue: '',
100
+ validate: zodValidator(z.string().min(1, 'Required')),
101
+ })
102
+ expect(field.validate().isValid).toBe(false)
103
+ })
104
+
105
+ it('validates the supplied value, not the stored value', () => {
106
+ const field = fields.text({
107
+ defaultValue: 'valid',
108
+ validate: zodValidator(z.string().min(1)),
109
+ })
110
+ expect(field.validate('').isValid).toBe(false)
111
+ })
112
+
113
+ it('catches ValidationError and returns structured failure', () => {
114
+ const field = fields.text({
115
+ defaultValue: 'x',
116
+ validate: () => { throw new ValidationError('custom-error') },
117
+ })
118
+ const result = field.validate()
119
+ expect(result.isValid).toBe(false)
120
+ expect(result.error).toBe('custom-error')
121
+ })
122
+
123
+ it('re-throws non-ValidationError exceptions', () => {
124
+ const field = fields.text({
125
+ defaultValue: 'x',
126
+ validate: () => { throw new Error('unexpected') },
127
+ })
128
+ expect(() => field.validate()).toThrow('unexpected')
129
+ })
130
+ })
131
+
132
+ describe('Field.isValid', () => {
133
+ it('is true when validator passes', () => {
134
+ const field = fields.text({ defaultValue: 'hello', validate: zodValidator(z.string().min(1)) })
135
+ expect(field.isValid).toBe(true)
136
+ })
137
+
138
+ it('is false when validator fails', () => {
139
+ const field = fields.text({ defaultValue: '', validate: zodValidator(z.string().min(1)) })
140
+ expect(field.isValid).toBe(false)
141
+ })
142
+ })
143
+
144
+ describe('Field error visibility', () => {
145
+ it('revealError sets isErrorRevealed to true', () => {
146
+ const field = fields.text({ defaultValue: '' })
147
+ field.revealError()
148
+ expect(field.isErrorRevealed).toBe(true)
149
+ })
150
+
151
+ it('hideError sets isErrorRevealed to false', () => {
152
+ const field = fields.text({ defaultValue: '' })
153
+ field.revealError()
154
+ field.hideError()
155
+ expect(field.isErrorRevealed).toBe(false)
156
+ })
157
+ })
158
+
159
+ describe('Field.props and transformers', () => {
160
+ it('props returns field name and field reference', () => {
161
+ const field = fields.text({ defaultValue: '', name: 'email' })
162
+ const props = field.props()
163
+ expect(props.name).toBe('email')
164
+ expect(props.field).toBe(field)
165
+ })
166
+
167
+ it('attachTransformer applies transformation to props', () => {
168
+ Field.attachTransformer('add-flag', (p) => ({ ...p, flagged: true }))
169
+ const field = fields.text({ defaultValue: '', name: 'x' })
170
+ expect((field.props() as any).flagged).toBe(true)
171
+ })
172
+
173
+ it('detachTransformer removes the transformation', () => {
174
+ Field.attachTransformer('to-remove', (p) => ({ ...p, extra: 1 }))
175
+ Field.detachTransformer('to-remove')
176
+ const field = fields.text({ defaultValue: '', name: 'x' })
177
+ expect((field.props() as any).extra).toBeUndefined()
178
+ })
179
+
180
+ it('multiple transformers compose in insertion order', () => {
181
+ Field.attachTransformer('first', (p) => ({ ...p, order: 'first' }))
182
+ Field.attachTransformer('second', (p) => ({ ...p, order: 'second' }))
183
+ const field = fields.text({ defaultValue: '' })
184
+ expect((field.props() as any).order).toBe('second')
185
+ })
186
+ })
@@ -0,0 +1,160 @@
1
+ import { describe, it, expect } from 'bun:test'
2
+ import { form } from '../lib/Form'
3
+ import { fields } from '../fields'
4
+ import { z } from 'zod'
5
+ import { zodValidator } from '../validators'
6
+ import './setup'
7
+
8
+ const makeBasicForm = () =>
9
+ form('basic', {
10
+ name: fields.text({ defaultValue: 'Alice' }),
11
+ age: fields.number({ defaultValue: 25 }),
12
+ })
13
+
14
+ const makeValidationForm = () =>
15
+ form('validation', {
16
+ email: fields.text({ validate: zodValidator(z.string().email('Invalid email')) }),
17
+ bio: fields.text({ defaultValue: 'hello', validate: zodValidator(z.string().min(1)) }),
18
+ })
19
+
20
+ describe('Form construction', () => {
21
+ it('sets field names from object keys', () => {
22
+ const f = makeBasicForm()
23
+ expect(f.fields.name.name).toBe('name')
24
+ expect(f.fields.age.name).toBe('age')
25
+ })
26
+
27
+ it('initializes values from field defaults', () => {
28
+ const f = makeBasicForm()
29
+ expect(f.values.name).toBe('Alice')
30
+ expect(f.values.age).toBe(25)
31
+ })
32
+ })
33
+
34
+ describe('Form.setValues', () => {
35
+ it('updates only keys present in source', () => {
36
+ const f = makeBasicForm()
37
+ f.setValues({ name: 'Bob' })
38
+ expect(f.values.name).toBe('Bob')
39
+ expect(f.values.age).toBe(25)
40
+ })
41
+
42
+ it('writes falsy string value', () => {
43
+ const f = makeBasicForm()
44
+ f.setValues({ name: '' })
45
+ expect(f.values.name).toBe('')
46
+ })
47
+
48
+ it('writes zero', () => {
49
+ const f = makeBasicForm()
50
+ f.setValues({ age: 0 })
51
+ expect(f.values.age).toBe(0)
52
+ })
53
+ })
54
+
55
+ describe('Form.resetValues', () => {
56
+ it('restores all fields to initial values', () => {
57
+ const f = makeBasicForm()
58
+ f.setValues({ name: 'Bob', age: 30 })
59
+ f.resetValues()
60
+ expect(f.values.name).toBe('Alice')
61
+ expect(f.values.age).toBe(25)
62
+ })
63
+
64
+ it('hides revealed errors', () => {
65
+ const f = makeValidationForm()
66
+ f.fields.email.revealError()
67
+ f.resetValues()
68
+ expect(f.fields.email.isErrorRevealed).toBe(false)
69
+ })
70
+ })
71
+
72
+ describe('Form.changed / isChanged', () => {
73
+ it('is false when no fields have changed', () => {
74
+ const f = makeBasicForm()
75
+ expect(f.changed()).toBe(false)
76
+ expect(f.isChanged).toBe(false)
77
+ })
78
+
79
+ it('is true when any field changes', () => {
80
+ const f = makeBasicForm()
81
+ f.fields.name.setValue('Bob')
82
+ expect(f.changed()).toBe(true)
83
+ })
84
+
85
+ it('is false after resetValues', () => {
86
+ const f = makeBasicForm()
87
+ f.setValues({ name: 'Bob' })
88
+ f.resetValues()
89
+ expect(f.changed()).toBe(false)
90
+ })
91
+ })
92
+
93
+ describe('Form.isValid', () => {
94
+ it('is true when all fields pass validation', () => {
95
+ const f = form('all-valid', {
96
+ name: fields.text({ defaultValue: 'Alice', validate: zodValidator(z.string().min(1)) }),
97
+ })
98
+ expect(f.isValid).toBe(true)
99
+ })
100
+
101
+ it('is false when any field fails validation', () => {
102
+ const f = makeValidationForm()
103
+ // email starts as undefined → fails z.string().email()
104
+ expect(f.isValid).toBe(false)
105
+ })
106
+ })
107
+
108
+ describe('Form.validate', () => {
109
+ it('returns a result map keyed by field name', () => {
110
+ const f = makeValidationForm()
111
+ const results = f.validate()
112
+ expect('email' in results).toBe(true)
113
+ expect('bio' in results).toBe(true)
114
+ })
115
+
116
+ it('reveals errors on all fields when revealErrors is true', () => {
117
+ const f = makeValidationForm()
118
+ expect(f.fields.email.isErrorRevealed).toBe(false)
119
+ f.validate({ revealErrors: true })
120
+ expect(f.fields.email.isErrorRevealed).toBe(true)
121
+ expect(f.fields.bio.isErrorRevealed).toBe(true)
122
+ })
123
+
124
+ it('validates only specified fields when fields option is provided', () => {
125
+ const f = makeValidationForm()
126
+ const results = f.validate({ fields: ['email'] })
127
+ expect('email' in results).toBe(true)
128
+ expect('bio' in results).toBe(false)
129
+ })
130
+ })
131
+
132
+ describe('Form.firstInvalid', () => {
133
+ it('returns undefined when all fields are valid', () => {
134
+ const f = form('all-valid', {
135
+ name: fields.text({ defaultValue: 'Alice', validate: zodValidator(z.string().min(1)) }),
136
+ })
137
+ expect(f.firstInvalid()).toBeUndefined()
138
+ })
139
+
140
+ it('returns the first invalid field with its validation result', () => {
141
+ const f = makeValidationForm()
142
+ const result = f.firstInvalid()
143
+ expect(result).toBeDefined()
144
+ expect(result!.validation.isValid).toBe(false)
145
+ expect(result!.field).toBe(f.fields.email)
146
+ })
147
+ })
148
+
149
+ describe('Form.register', () => {
150
+ it('returns props for a known field', () => {
151
+ const f = makeBasicForm()
152
+ const props = f.register('name')
153
+ expect(props.field).toBe(f.fields.name)
154
+ })
155
+
156
+ it('throws for an unknown field key', () => {
157
+ const f = makeBasicForm()
158
+ expect(() => f.register('nonexistent' as any)).toThrow()
159
+ })
160
+ })
@@ -0,0 +1,90 @@
1
+ import { describe, it, expect } from 'bun:test'
2
+ import { fields } from '../fields'
3
+ import { z } from 'zod'
4
+ import { zodValidator } from '../validators'
5
+ import './setup'
6
+
7
+ describe('TextField', () => {
8
+ it('is valid for any non-empty string by default', () => {
9
+ const f = fields.text({ defaultValue: 'hello' })
10
+ expect(f.isValid).toBe(true)
11
+ })
12
+
13
+ it('is valid for undefined by default (z.string().optional())', () => {
14
+ const f = fields.text({})
15
+ expect(f.isValid).toBe(true)
16
+ })
17
+
18
+ it('fails with a custom validator', () => {
19
+ const f = fields.text({
20
+ defaultValue: 'not-an-email',
21
+ validate: zodValidator(z.string().email()),
22
+ })
23
+ expect(f.isValid).toBe(false)
24
+ })
25
+
26
+ it('has _type TEXT', () => {
27
+ expect(fields.text({})._type).toBe('TEXT')
28
+ })
29
+ })
30
+
31
+ describe('NumberField', () => {
32
+ it('is valid for a number within default range', () => {
33
+ const f = fields.number({ defaultValue: 100 })
34
+ expect(f.isValid).toBe(true)
35
+ })
36
+
37
+ it('is invalid when value is below min', () => {
38
+ const f = fields.number({ defaultValue: -1, min: 0 })
39
+ expect(f.isValid).toBe(false)
40
+ })
41
+
42
+ it('is invalid when value exceeds max', () => {
43
+ const f = fields.number({ defaultValue: 100, max: 50 })
44
+ expect(f.isValid).toBe(false)
45
+ })
46
+
47
+ it('supports array (range) defaultValue', () => {
48
+ const f = fields.number({ defaultValue: [10, 90] })
49
+ expect(f.isValid).toBe(true)
50
+ })
51
+
52
+ it('array range is invalid when any element is out of bounds', () => {
53
+ const f = fields.number({ defaultValue: [-1, 90], min: 0 })
54
+ expect(f.isValid).toBe(false)
55
+ })
56
+
57
+ it('has _type NUMBER', () => {
58
+ expect(fields.number({ defaultValue: 0 })._type).toBe('NUMBER')
59
+ })
60
+ })
61
+
62
+ describe('ListField', () => {
63
+ const makeItemField = () => fields.text({ validate: zodValidator(z.string().min(1)) })
64
+
65
+ it('is valid when empty', () => {
66
+ const f = fields.list({ item: makeItemField(), defaultValue: [] })
67
+ expect(f.isValid).toBe(true)
68
+ })
69
+
70
+ it('is valid when all items pass item validator', () => {
71
+ const f = fields.list({ item: makeItemField(), defaultValue: ['a', 'b', 'c'] })
72
+ expect(f.isValid).toBe(true)
73
+ })
74
+
75
+ it('is invalid when any item fails item validator', () => {
76
+ const f = fields.list({ item: makeItemField(), defaultValue: ['a', '', 'c'] })
77
+ expect(f.isValid).toBe(false)
78
+ })
79
+
80
+ it('is always valid when item has no validator', () => {
81
+ const noValidatorItem = fields.text({})
82
+ const f = fields.list({ item: noValidatorItem, defaultValue: ['', ''] })
83
+ expect(f.isValid).toBe(true)
84
+ })
85
+
86
+ it('has _type LIST', () => {
87
+ const f = fields.list({ item: fields.text({}), defaultValue: [] })
88
+ expect(f._type).toBe('LIST')
89
+ })
90
+ })
@@ -0,0 +1,111 @@
1
+ import { describe, it, expect } from 'bun:test'
2
+ import { renderHook, act, waitFor } from '@testing-library/react'
3
+ import { useForm, form } from '../lib/Form'
4
+ import { fields } from '../fields'
5
+ import { z } from 'zod'
6
+ import { zodValidator } from '../validators'
7
+ import './setup'
8
+
9
+ describe('useForm', () => {
10
+ it('creates a form with the given id', () => {
11
+ const { result } = renderHook(() =>
12
+ useForm('my-form', { name: fields.text({ defaultValue: 'Alice' }) })
13
+ )
14
+ expect(result.current.id).toBe('my-form')
15
+ })
16
+
17
+ it('returns a stable reference across re-renders', () => {
18
+ const { result, rerender } = renderHook(() =>
19
+ useForm('stable', { name: fields.text({ defaultValue: '' }) })
20
+ )
21
+ const first = result.current
22
+ rerender()
23
+ expect(result.current).toBe(first)
24
+ })
25
+
26
+ it('sets field names from the definition keys', () => {
27
+ const { result } = renderHook(() =>
28
+ useForm('named', {
29
+ email: fields.text({}),
30
+ password: fields.text({}),
31
+ })
32
+ )
33
+ expect(result.current.fields.email.name).toBe('email')
34
+ expect(result.current.fields.password.name).toBe('password')
35
+ })
36
+ })
37
+
38
+ describe('Form.useInitialize', () => {
39
+ it('does not change values when called with null', () => {
40
+ const f = form('init-null', { name: fields.text({ defaultValue: 'original' }) })
41
+ renderHook(() => { f.useInitialize(null) })
42
+ expect(f.values.name).toBe('original')
43
+ })
44
+
45
+ it('does not change values when called with undefined', () => {
46
+ const f = form('init-undef', { name: fields.text({ defaultValue: 'original' }) })
47
+ renderHook(() => { f.useInitialize(undefined) })
48
+ expect(f.values.name).toBe('original')
49
+ })
50
+
51
+ it('sets values when non-null data is provided', async () => {
52
+ const f = form('init-values', { name: fields.text({ defaultValue: '' }) })
53
+ const initValues = { name: 'Bob' }
54
+ renderHook(() => { f.useInitialize(initValues) })
55
+ await waitFor(() => {
56
+ expect(f.values.name).toBe('Bob')
57
+ })
58
+ })
59
+
60
+ it('re-applies values when deps change', async () => {
61
+ const f = form('init-deps', { name: fields.text({ defaultValue: '' }) })
62
+ let trigger = 0
63
+ const { rerender } = renderHook(() => {
64
+ f.useInitialize({ name: `value-${trigger}` }, [trigger])
65
+ })
66
+ await waitFor(() => { expect(f.values.name).toBe('value-0') })
67
+
68
+ trigger = 1
69
+ rerender()
70
+ await waitFor(() => { expect(f.values.name).toBe('value-1') })
71
+ })
72
+ })
73
+
74
+ describe('Form.useShared', () => {
75
+ it('returns the initial selected value', () => {
76
+ const f = form('shared-init', { count: fields.number({ defaultValue: 5 }) })
77
+ const { result } = renderHook(() => f.useShared((frm) => frm.values.count))
78
+ expect(result.current).toBe(5)
79
+ })
80
+
81
+ it('updates when the underlying state changes', async () => {
82
+ const f = form('shared-update', { count: fields.number({ defaultValue: 0 }) })
83
+ const { result } = renderHook(() => f.useShared((frm) => frm.values.count))
84
+ expect(result.current).toBe(0)
85
+
86
+ act(() => { f.fields.count.setValue(42) })
87
+
88
+ await waitFor(() => { expect(result.current).toBe(42) })
89
+ })
90
+
91
+ it('does not reset on unmount', () => {
92
+ const f = form('shared-no-reset', { name: fields.text({ defaultValue: 'init' }) })
93
+ f.fields.name.setValue('changed')
94
+ const { unmount } = renderHook(() => f.useShared((frm) => frm.values.name))
95
+ unmount()
96
+ expect(f.values.name).toBe('changed')
97
+ })
98
+ })
99
+
100
+ describe('Form.use (resets on unmount)', () => {
101
+ it('resets all field values when the component unmounts', () => {
102
+ const f = form('use-reset', { name: fields.text({ defaultValue: 'initial' }) })
103
+ f.fields.name.setValue('changed')
104
+
105
+ const { unmount } = renderHook(() => f.use((frm) => frm.values.name))
106
+ expect(f.values.name).toBe('changed')
107
+
108
+ unmount()
109
+ expect(f.values.name).toBe('initial')
110
+ })
111
+ })
@@ -0,0 +1,25 @@
1
+ import { afterEach } from 'bun:test'
2
+ import { cleanup } from '@testing-library/react'
3
+ import { z } from 'zod'
4
+ import { zodValidator } from '../validators'
5
+ import { fields } from '../fields'
6
+ import { form } from '../lib/Form'
7
+
8
+ export const makeTextField = (defaultValue = '') =>
9
+ fields.text({ defaultValue })
10
+
11
+ export const makeRequiredTextField = () =>
12
+ fields.text({
13
+ defaultValue: '',
14
+ validate: zodValidator(z.string().min(1, 'Required')),
15
+ })
16
+
17
+ export const makeLoginForm = () =>
18
+ form('login', {
19
+ email: fields.text({ validate: zodValidator(z.string().email('Invalid email')) }),
20
+ password: fields.text({ validate: zodValidator(z.string().min(8, 'Too short')) }),
21
+ })
22
+
23
+ afterEach(() => {
24
+ cleanup()
25
+ })
@@ -14,7 +14,7 @@ export type FieldOptions<
14
14
  Validate extends Validator<T, any, any>
15
15
  > = {
16
16
  name?: string
17
- defaultValue?: T | null
17
+ defaultValue?: T | null | (() => T) | (() => Promise<T>)
18
18
  state?: FieldState<T>
19
19
 
20
20
  validate?: Validate
@@ -13,7 +13,8 @@ export interface IFieldRef<T> {
13
13
 
14
14
 
15
15
  export interface IFieldProps {
16
-
16
+ name?: string
17
+ field: any
17
18
  }
18
19
 
19
20
  export type PropTransformer = (props: AnyRecord) => AnyRecord