@reckona/mreact-forms 0.0.181 → 0.0.183
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.
- package/README.md +2 -0
- package/dist/index.d.ts +26 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +210 -10
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/index.ts +320 -15
package/README.md
CHANGED
|
@@ -108,7 +108,9 @@ await inviteForm.submit((values) => {
|
|
|
108
108
|
|
|
109
109
|
- `createForm()` creates reactive form state.
|
|
110
110
|
- `form.field(name).bind()` returns `{ value, onInput, onChange, onBlur }` and handles string values and boolean checkbox-style values. Pass `{ event: "change" }` to update from `onChange`; the default updates from `onInput`.
|
|
111
|
+
- `form.fieldArray(name)` returns stable keyed rows and `append`, `insert`, `move`, `remove`, and `swap` helpers for array-valued form fields.
|
|
111
112
|
- Field state includes `validating`, which is true while the latest async field validator is pending. Slower stale validator results are ignored.
|
|
113
|
+
- Field validators can use `{ validate, deps }` when one field should be revalidated after another field changes or blurs.
|
|
112
114
|
- `setServerErrors()` applies errors returned by a route handler or server action.
|
|
113
115
|
- `form.submit()` dedupes concurrent submissions and keeps `submitting` true until the active validation and handler finish.
|
|
114
116
|
- `form.reset()` restores the initial values.
|
package/dist/index.d.ts
CHANGED
|
@@ -8,6 +8,13 @@ export type FieldName<TValues extends FormValues> = Extract<keyof TValues, strin
|
|
|
8
8
|
export type FormErrors<TValues extends FormValues> = Partial<Record<FieldName<TValues> | "root", string[]>>;
|
|
9
9
|
/** Validates one field value against the full form values object. */
|
|
10
10
|
export type FieldValidator<TValue, TValues extends FormValues> = (value: TValue, values: TValues) => readonly string[] | string | undefined | Promise<readonly string[] | string | undefined>;
|
|
11
|
+
/** Configures one field validator and which other fields should re-trigger it. */
|
|
12
|
+
export interface FieldValidationConfig<TValue, TValues extends FormValues> {
|
|
13
|
+
deps?: readonly FieldName<TValues>[] | undefined;
|
|
14
|
+
validate: FieldValidator<TValue, TValues>;
|
|
15
|
+
}
|
|
16
|
+
/** Provides either a validator function or a validator descriptor with dependencies. */
|
|
17
|
+
export type FieldValidationEntry<TValue, TValues extends FormValues> = FieldValidator<TValue, TValues> | FieldValidationConfig<TValue, TValues>;
|
|
11
18
|
/** Names the form events that can trigger validation. */
|
|
12
19
|
export type FormValidateMode = "change" | "blur" | "submit";
|
|
13
20
|
/** Describes the complete reactive state tracked by a form instance. */
|
|
@@ -48,10 +55,27 @@ export interface FieldApi<TValues extends FormValues, Name extends FieldName<TVa
|
|
|
48
55
|
blur(): Promise<void>;
|
|
49
56
|
setValue(value: TValues[Name]): Promise<void>;
|
|
50
57
|
}
|
|
58
|
+
/** Extracts an array field's item type. */
|
|
59
|
+
export type ArrayFieldValue<TValues extends FormValues, Name extends FieldName<TValues>> = TValues[Name] extends readonly (infer Item)[] ? Item : never;
|
|
60
|
+
/** Describes one rendered row in a form array field. */
|
|
61
|
+
export interface FieldArrayRow<TValue> {
|
|
62
|
+
index: number;
|
|
63
|
+
key: string;
|
|
64
|
+
value: TValue;
|
|
65
|
+
}
|
|
66
|
+
/** Exposes stable keyed row state and mutation helpers for an array field. */
|
|
67
|
+
export interface FieldArrayApi<TValues extends FormValues, Name extends FieldName<TValues>> {
|
|
68
|
+
readonly fields: ReadonlyCell<Array<FieldArrayRow<ArrayFieldValue<TValues, Name>>>>;
|
|
69
|
+
append(value: ArrayFieldValue<TValues, Name>): Promise<void>;
|
|
70
|
+
insert(index: number, value: ArrayFieldValue<TValues, Name>): Promise<void>;
|
|
71
|
+
move(from: number, to: number): Promise<void>;
|
|
72
|
+
remove(index: number): Promise<void>;
|
|
73
|
+
swap(first: number, second: number): Promise<void>;
|
|
74
|
+
}
|
|
51
75
|
interface BaseCreateFormOptions<TValues extends FormValues> {
|
|
52
76
|
initialValues: TValues;
|
|
53
77
|
validate?: Partial<{
|
|
54
|
-
[Name in FieldName<TValues>]:
|
|
78
|
+
[Name in FieldName<TValues>]: FieldValidationEntry<TValues[Name], TValues>;
|
|
55
79
|
}> | undefined;
|
|
56
80
|
validateOn?: FormValidateMode | readonly FormValidateMode[] | undefined;
|
|
57
81
|
}
|
|
@@ -94,6 +118,7 @@ export interface ServerActionErrors<TValues extends FormValues> {
|
|
|
94
118
|
/** Provides reactive form state, field access, validation, submit, reset, and error controls. */
|
|
95
119
|
export interface FormApi<TValues extends FormValues, TSubmitValues> {
|
|
96
120
|
readonly state: ReadonlyCell<FormState<TValues>>;
|
|
121
|
+
fieldArray<Name extends FieldName<TValues>>(name: Name): FieldArrayApi<TValues, Name>;
|
|
97
122
|
field<Name extends FieldName<TValues>>(name: Name): FieldApi<TValues, Name>;
|
|
98
123
|
getValues(): TValues;
|
|
99
124
|
reset(values?: TValues): void;
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,KAAK,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAClF,OAAO,EAAE,KAAK,gBAAgB,EAA0B,MAAM,sBAAsB,CAAC;AAErF,8DAA8D;AAC9D,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAEjD,4DAA4D;AAC5D,MAAM,MAAM,SAAS,CAAC,OAAO,SAAS,UAAU,IAAI,OAAO,CAAC,MAAM,OAAO,EAAE,MAAM,CAAC,CAAC;AAEnF,2EAA2E;AAC3E,MAAM,MAAM,UAAU,CAAC,OAAO,SAAS,UAAU,IAAI,OAAO,CAC1D,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,GAAG,MAAM,EAAE,MAAM,EAAE,CAAC,CAC9C,CAAC;AAEF,qEAAqE;AACrE,MAAM,MAAM,cAAc,CAAC,MAAM,EAAE,OAAO,SAAS,UAAU,IAAI,CAC/D,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,OAAO,KACZ,SAAS,MAAM,EAAE,GAAG,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,SAAS,MAAM,EAAE,GAAG,MAAM,GAAG,SAAS,CAAC,CAAC;AAE9F,kFAAkF;AAClF,MAAM,WAAW,qBAAqB,CAAC,MAAM,EAAE,OAAO,SAAS,UAAU;IACvE,IAAI,CAAC,EAAE,SAAS,SAAS,CAAC,OAAO,CAAC,EAAE,GAAG,SAAS,CAAC;IACjD,QAAQ,EAAE,cAAc,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC3C;AAED,wFAAwF;AACxF,MAAM,MAAM,oBAAoB,CAAC,MAAM,EAAE,OAAO,SAAS,UAAU,IAC/D,cAAc,CAAC,MAAM,EAAE,OAAO,CAAC,GAC/B,qBAAqB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAE3C,yDAAyD;AACzD,MAAM,MAAM,gBAAgB,GAAG,QAAQ,GAAG,MAAM,GAAG,QAAQ,CAAC;AAE5D,wEAAwE;AACxE,MAAM,WAAW,SAAS,CAAC,OAAO,SAAS,UAAU;IACnD,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,UAAU,CAAC,OAAO,CAAC,CAAC;IAC5B,aAAa,EAAE,OAAO,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,OAAO,CAAC;IACpB,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;IACtD,KAAK,EAAE,OAAO,CAAC;IACf,UAAU,EAAE,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;IACzD,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,sDAAsD;AACtD,MAAM,WAAW,UAAU,CAAC,MAAM;IAChC,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,OAAO,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,qFAAqF;AACrF,MAAM,WAAW,YAAY,CAAC,MAAM;IAClC,MAAM,CAAC,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpC,QAAQ,CAAC,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC,OAAO,CAAC,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACrC,KAAK,EAAE,MAAM,CAAC;CACf;AAED,0DAA0D;AAC1D,MAAM,WAAW,mBAAmB;IAClC,KAAK,CAAC,EAAE,QAAQ,GAAG,OAAO,GAAG,SAAS,CAAC;CACxC;AAED,6EAA6E;AAC7E,MAAM,WAAW,QAAQ,CAAC,OAAO,SAAS,UAAU,EAAE,IAAI,SAAS,SAAS,CAAC,OAAO,CAAC;IACnF,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACxD,IAAI,CAAC,OAAO,CAAC,EAAE,mBAAmB,GAAG,YAAY,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;IACjE,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/C;AAED,2CAA2C;AAC3C,MAAM,MAAM,eAAe,CACzB,OAAO,SAAS,UAAU,EAC1B,IAAI,SAAS,SAAS,CAAC,OAAO,CAAC,IAC7B,OAAO,CAAC,IAAI,CAAC,SAAS,SAAS,CAAC,MAAM,IAAI,CAAC,EAAE,GAAG,IAAI,GAAG,KAAK,CAAC;AAEjE,wDAAwD;AACxD,MAAM,WAAW,aAAa,CAAC,MAAM;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;CACf;AAED,8EAA8E;AAC9E,MAAM,WAAW,aAAa,CAAC,OAAO,SAAS,UAAU,EAAE,IAAI,SAAS,SAAS,CAAC,OAAO,CAAC;IACxF,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC,KAAK,CAAC,aAAa,CAAC,eAAe,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;IACpF,MAAM,CAAC,KAAK,EAAE,eAAe,CAAC,OAAO,EAAE,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7D,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,eAAe,CAAC,OAAO,EAAE,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5E,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9C,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACrC,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACpD;AAED,UAAU,qBAAqB,CAAC,OAAO,SAAS,UAAU;IACxD,aAAa,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,EACL,OAAO,CAAC;SACL,IAAI,IAAI,SAAS,CAAC,OAAO,CAAC,GAAG,oBAAoB,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC;KAC3E,CAAC,GACF,SAAS,CAAC;IACd,UAAU,CAAC,EAAE,gBAAgB,GAAG,SAAS,gBAAgB,EAAE,GAAG,SAAS,CAAC;CACzE;AAED,iFAAiF;AACjF,MAAM,WAAW,8BAA8B,CAC7C,OAAO,SAAS,UAAU,CAC1B,SAAQ,qBAAqB,CAAC,OAAO,CAAC;IACtC,MAAM,CAAC,EAAE,SAAS,CAAC;CACpB;AAED,kGAAkG;AAClG,MAAM,WAAW,2BAA2B,CAC1C,OAAO,SAAS,UAAU,EAC1B,aAAa,CACb,SAAQ,qBAAqB,CAAC,OAAO,CAAC;IACtC,MAAM,EAAE,gBAAgB,CAAC,OAAO,EAAE,aAAa,CAAC,CAAC;CAClD;AAED,uGAAuG;AACvG,MAAM,MAAM,iBAAiB,CAAC,OAAO,SAAS,UAAU,EAAE,aAAa,GAAG,OAAO,IAC7E,8BAA8B,CAAC,OAAO,CAAC,GACvC,2BAA2B,CAAC,OAAO,EAAE,aAAa,CAAC,CAAC;AAExD,oFAAoF;AACpF,MAAM,MAAM,oBAAoB,CAAC,OAAO,SAAS,UAAU,EAAE,aAAa,IACtE;IACE,OAAO,EAAE,IAAI,CAAC;IACd,KAAK,EAAE,aAAa,CAAC;CACtB,GACD;IACE,MAAM,EAAE,UAAU,CAAC,OAAO,CAAC,CAAC;IAC5B,OAAO,EAAE,KAAK,CAAC;CAChB,CAAC;AAEN,sFAAsF;AACtF,MAAM,MAAM,gBAAgB,CAAC,OAAO,SAAS,UAAU,EAAE,OAAO,IAC5D;IACE,IAAI,EAAE,OAAO,CAAC;IACd,MAAM,EAAE,SAAS,CAAC;CACnB,GACD;IACE,MAAM,EAAE,WAAW,CAAC;CACrB,GACD;IACE,MAAM,EAAE,UAAU,CAAC,OAAO,CAAC,CAAC;IAC5B,MAAM,EAAE,SAAS,CAAC;CACnB,GACD;IACE,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,OAAO,CAAC;CACjB,CAAC;AAEN,oEAAoE;AACpE,MAAM,WAAW,kBAAkB,CAAC,OAAO,SAAS,UAAU;IAC5D,WAAW,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC,CAAC,GAAG,SAAS,CAAC;IACjF,UAAU,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC;CAC5C;AAED,iGAAiG;AACjG,MAAM,WAAW,OAAO,CAAC,OAAO,SAAS,UAAU,EAAE,aAAa;IAChE,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC;IACjD,UAAU,CAAC,IAAI,SAAS,SAAS,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,IAAI,GAAG,aAAa,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IACtF,KAAK,CAAC,IAAI,SAAS,SAAS,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,IAAI,GAAG,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IAC5E,SAAS,IAAI,OAAO,CAAC;IACrB,KAAK,CAAC,MAAM,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IAC9B,SAAS,CAAC,MAAM,EAAE,UAAU,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC;IAC7C,eAAe,CAAC,MAAM,EAAE,kBAAkB,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC;IAC3D,QAAQ,CAAC,IAAI,SAAS,SAAS,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3F,MAAM,CAAC,OAAO,EACZ,OAAO,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,GAC7D,OAAO,CAAC,gBAAgB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC;IAC/C,QAAQ,IAAI,OAAO,CAAC,oBAAoB,CAAC,OAAO,EAAE,aAAa,CAAC,CAAC,CAAC;CACnE;AAED,mGAAmG;AACnG,wBAAgB,UAAU,CAAC,OAAO,SAAS,UAAU,EACnD,OAAO,EAAE,8BAA8B,CAAC,OAAO,CAAC,GAC/C,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;AAC7B,wBAAgB,UAAU,CAAC,OAAO,SAAS,UAAU,EAAE,aAAa,EAClE,OAAO,EAAE,2BAA2B,CAAC,OAAO,EAAE,aAAa,CAAC,GAC3D,OAAO,CAAC,OAAO,EAAE,aAAa,CAAC,CAAC;AAgpBnC,oEAAoE;AACpE,YAAY,EACV,wBAAwB,EACxB,yBAAyB,EACzB,gBAAgB,GACjB,MAAM,sBAAsB,CAAC;AAC9B,yFAAyF;AACzF,OAAO,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { cell } from "@reckona/mreact-reactive-core";
|
|
1
|
+
import { cell, computed } from "@reckona/mreact-reactive-core";
|
|
2
2
|
import { validateStandardSchema } from "./standard-schema.js";
|
|
3
3
|
export function createForm(options) {
|
|
4
4
|
const validateOn = new Set(Array.isArray(options.validateOn) ? options.validateOn : [options.validateOn ?? "submit"]);
|
|
@@ -16,6 +16,11 @@ export function createForm(options) {
|
|
|
16
16
|
});
|
|
17
17
|
const validationGenerations = new Map();
|
|
18
18
|
const dirtyFields = new Set();
|
|
19
|
+
const dependentFields = buildDependentFields(options.validate);
|
|
20
|
+
const fieldArrayKeys = new Map();
|
|
21
|
+
const fieldStateCells = new Map();
|
|
22
|
+
const fieldArrayCells = new Map();
|
|
23
|
+
let nextFieldArrayKey = 0;
|
|
19
24
|
let activeSubmit;
|
|
20
25
|
function commit(patch, dirty = dirtyFields.size > 0) {
|
|
21
26
|
const previous = state.get();
|
|
@@ -30,8 +35,19 @@ export function createForm(options) {
|
|
|
30
35
|
function setErrors(errors) {
|
|
31
36
|
commit({ errors: normalizeErrors(errors) });
|
|
32
37
|
}
|
|
38
|
+
function invalidateFieldValidations(names) {
|
|
39
|
+
if (names.length === 0) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const validating = { ...state.get().validating };
|
|
43
|
+
for (const name of names) {
|
|
44
|
+
validationGenerations.set(name, (validationGenerations.get(name) ?? 0) + 1);
|
|
45
|
+
validating[name] = false;
|
|
46
|
+
}
|
|
47
|
+
commit({ validating });
|
|
48
|
+
}
|
|
33
49
|
async function validateField(name) {
|
|
34
|
-
const validator = options.validate?.[name];
|
|
50
|
+
const validator = validatorForField(options.validate?.[name]);
|
|
35
51
|
const generation = (validationGenerations.get(name) ?? 0) + 1;
|
|
36
52
|
validationGenerations.set(name, generation);
|
|
37
53
|
if (validator === undefined) {
|
|
@@ -74,7 +90,7 @@ export function createForm(options) {
|
|
|
74
90
|
},
|
|
75
91
|
});
|
|
76
92
|
if (validateOn.has("change")) {
|
|
77
|
-
await
|
|
93
|
+
await validateFields([name, ...dependentFieldsFor(name)]);
|
|
78
94
|
}
|
|
79
95
|
}
|
|
80
96
|
async function blurField(name) {
|
|
@@ -85,18 +101,21 @@ export function createForm(options) {
|
|
|
85
101
|
},
|
|
86
102
|
});
|
|
87
103
|
if (validateOn.has("blur")) {
|
|
88
|
-
await
|
|
104
|
+
await validateFields([name, ...dependentFieldsFor(name)]);
|
|
89
105
|
}
|
|
90
106
|
}
|
|
91
107
|
async function validateForm() {
|
|
92
108
|
const values = state.get().values;
|
|
93
109
|
const errors = {};
|
|
94
|
-
|
|
110
|
+
const fieldNames = Object.keys(options.validate ?? {});
|
|
111
|
+
invalidateFieldValidations(fieldNames);
|
|
112
|
+
for (const name of fieldNames) {
|
|
95
113
|
const validator = options.validate?.[name];
|
|
96
|
-
|
|
114
|
+
const validate = validatorForField(validator);
|
|
115
|
+
if (validate === undefined) {
|
|
97
116
|
continue;
|
|
98
117
|
}
|
|
99
|
-
const fieldErrors = normalizeFieldErrors(await
|
|
118
|
+
const fieldErrors = normalizeFieldErrors(await validate(values[name], values));
|
|
100
119
|
if (fieldErrors.length > 0) {
|
|
101
120
|
errors[name] = fieldErrors;
|
|
102
121
|
}
|
|
@@ -127,13 +146,132 @@ export function createForm(options) {
|
|
|
127
146
|
value: values,
|
|
128
147
|
};
|
|
129
148
|
}
|
|
149
|
+
async function validateFields(names) {
|
|
150
|
+
const uniqueNames = [...new Set(names)];
|
|
151
|
+
await Promise.all(uniqueNames.map((fieldName) => validateField(fieldName)));
|
|
152
|
+
}
|
|
153
|
+
function dependentFieldsFor(name) {
|
|
154
|
+
return dependentFields.get(name) ?? [];
|
|
155
|
+
}
|
|
156
|
+
function arrayValues(name) {
|
|
157
|
+
const value = state.get().values[name];
|
|
158
|
+
return Array.isArray(value) ? [...value] : [];
|
|
159
|
+
}
|
|
160
|
+
function createFieldArrayKey(name) {
|
|
161
|
+
const key = `${name}:${nextFieldArrayKey}`;
|
|
162
|
+
nextFieldArrayKey += 1;
|
|
163
|
+
return key;
|
|
164
|
+
}
|
|
165
|
+
function ensureFieldArrayKeys(name, length) {
|
|
166
|
+
const keys = fieldArrayKeys.get(name) ?? [];
|
|
167
|
+
if (keys.length > length) {
|
|
168
|
+
keys.length = length;
|
|
169
|
+
}
|
|
170
|
+
while (keys.length < length) {
|
|
171
|
+
keys.push(createFieldArrayKey(name));
|
|
172
|
+
}
|
|
173
|
+
fieldArrayKeys.set(name, keys);
|
|
174
|
+
return keys;
|
|
175
|
+
}
|
|
176
|
+
function fieldArrayRows(name) {
|
|
177
|
+
const values = arrayValues(name);
|
|
178
|
+
const keys = ensureFieldArrayKeys(name, values.length);
|
|
179
|
+
return values.map((value, index) => ({
|
|
180
|
+
index,
|
|
181
|
+
key: keys[index] ?? createFieldArrayKey(name),
|
|
182
|
+
value,
|
|
183
|
+
}));
|
|
184
|
+
}
|
|
185
|
+
function fieldArrayCell(name) {
|
|
186
|
+
const existing = fieldArrayCells.get(name);
|
|
187
|
+
if (existing !== undefined) {
|
|
188
|
+
return existing;
|
|
189
|
+
}
|
|
190
|
+
const next = computed(() => fieldArrayRows(name), { equals: fieldArrayRowsEqual });
|
|
191
|
+
fieldArrayCells.set(name, next);
|
|
192
|
+
return next;
|
|
193
|
+
}
|
|
194
|
+
function fieldStateCell(name) {
|
|
195
|
+
const existing = fieldStateCells.get(name);
|
|
196
|
+
if (existing !== undefined) {
|
|
197
|
+
return existing;
|
|
198
|
+
}
|
|
199
|
+
const next = computed(() => fieldState(state.get(), name), { equals: fieldStateEquals });
|
|
200
|
+
fieldStateCells.set(name, next);
|
|
201
|
+
return next;
|
|
202
|
+
}
|
|
203
|
+
async function setArrayValue(name, values, keys) {
|
|
204
|
+
fieldArrayKeys.set(name, keys);
|
|
205
|
+
await setValue(name, values);
|
|
206
|
+
}
|
|
207
|
+
async function validateArrayField(name) {
|
|
208
|
+
if (validateOn.has("change")) {
|
|
209
|
+
await validateFields([name, ...dependentFieldsFor(name)]);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
130
212
|
return {
|
|
131
213
|
state,
|
|
132
|
-
|
|
214
|
+
fieldArray(name) {
|
|
133
215
|
return {
|
|
134
|
-
|
|
135
|
-
|
|
216
|
+
fields: fieldArrayCell(name),
|
|
217
|
+
async append(value) {
|
|
218
|
+
const values = arrayValues(name);
|
|
219
|
+
const keys = [...ensureFieldArrayKeys(name, values.length), createFieldArrayKey(name)];
|
|
220
|
+
values.push(value);
|
|
221
|
+
await setArrayValue(name, values, keys);
|
|
222
|
+
},
|
|
223
|
+
async insert(index, value) {
|
|
224
|
+
const values = arrayValues(name);
|
|
225
|
+
const insertIndex = clampIndex(index, 0, values.length);
|
|
226
|
+
const keys = [...ensureFieldArrayKeys(name, values.length)];
|
|
227
|
+
values.splice(insertIndex, 0, value);
|
|
228
|
+
keys.splice(insertIndex, 0, createFieldArrayKey(name));
|
|
229
|
+
await setArrayValue(name, values, keys);
|
|
230
|
+
},
|
|
231
|
+
async move(from, to) {
|
|
232
|
+
const values = arrayValues(name);
|
|
233
|
+
const keys = [...ensureFieldArrayKeys(name, values.length)];
|
|
234
|
+
if (!isArrayIndex(from, values.length)) {
|
|
235
|
+
await validateArrayField(name);
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
const [value] = values.splice(from, 1);
|
|
239
|
+
const [key] = keys.splice(from, 1);
|
|
240
|
+
const insertIndex = clampIndex(to, 0, values.length);
|
|
241
|
+
values.splice(insertIndex, 0, value);
|
|
242
|
+
keys.splice(insertIndex, 0, key);
|
|
243
|
+
await setArrayValue(name, values, keys);
|
|
244
|
+
},
|
|
245
|
+
async remove(index) {
|
|
246
|
+
const values = arrayValues(name);
|
|
247
|
+
const keys = [...ensureFieldArrayKeys(name, values.length)];
|
|
248
|
+
if (!isArrayIndex(index, values.length)) {
|
|
249
|
+
await validateArrayField(name);
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
values.splice(index, 1);
|
|
253
|
+
keys.splice(index, 1);
|
|
254
|
+
await setArrayValue(name, values, keys);
|
|
255
|
+
},
|
|
256
|
+
async swap(first, second) {
|
|
257
|
+
const values = arrayValues(name);
|
|
258
|
+
const keys = [...ensureFieldArrayKeys(name, values.length)];
|
|
259
|
+
if (!isArrayIndex(first, values.length) || !isArrayIndex(second, values.length)) {
|
|
260
|
+
await validateArrayField(name);
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
[values[first], values[second]] = [
|
|
264
|
+
values[second],
|
|
265
|
+
values[first],
|
|
266
|
+
];
|
|
267
|
+
[keys[first], keys[second]] = [keys[second], keys[first]];
|
|
268
|
+
await setArrayValue(name, values, keys);
|
|
136
269
|
},
|
|
270
|
+
};
|
|
271
|
+
},
|
|
272
|
+
field(name) {
|
|
273
|
+
return {
|
|
274
|
+
state: fieldStateCell(name),
|
|
137
275
|
bind(bindingOptions = {}) {
|
|
138
276
|
const bindingEvent = bindingOptions.event ?? "input";
|
|
139
277
|
const updateValue = async (inputEvent) => {
|
|
@@ -173,6 +311,7 @@ export function createForm(options) {
|
|
|
173
311
|
}
|
|
174
312
|
initialValues = cloneValues(values);
|
|
175
313
|
dirtyFields.clear();
|
|
314
|
+
fieldArrayKeys.clear();
|
|
176
315
|
commit({
|
|
177
316
|
errors: {},
|
|
178
317
|
initialValues,
|
|
@@ -258,6 +397,41 @@ function fieldState(formState, name) {
|
|
|
258
397
|
value: formState.values[name],
|
|
259
398
|
};
|
|
260
399
|
}
|
|
400
|
+
function fieldStateEquals(previous, next) {
|
|
401
|
+
return (previous.dirty === next.dirty &&
|
|
402
|
+
previous.touched === next.touched &&
|
|
403
|
+
previous.validating === next.validating &&
|
|
404
|
+
Object.is(previous.value, next.value) &&
|
|
405
|
+
stringArraysEqual(previous.errors, next.errors));
|
|
406
|
+
}
|
|
407
|
+
function fieldArrayRowsEqual(previous, next) {
|
|
408
|
+
if (previous.length !== next.length) {
|
|
409
|
+
return false;
|
|
410
|
+
}
|
|
411
|
+
for (let index = 0; index < previous.length; index += 1) {
|
|
412
|
+
const previousRow = previous[index];
|
|
413
|
+
const nextRow = next[index];
|
|
414
|
+
if (previousRow === undefined ||
|
|
415
|
+
nextRow === undefined ||
|
|
416
|
+
previousRow.index !== nextRow.index ||
|
|
417
|
+
previousRow.key !== nextRow.key ||
|
|
418
|
+
!Object.is(previousRow.value, nextRow.value)) {
|
|
419
|
+
return false;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
return true;
|
|
423
|
+
}
|
|
424
|
+
function stringArraysEqual(previous, next) {
|
|
425
|
+
if (previous.length !== next.length) {
|
|
426
|
+
return false;
|
|
427
|
+
}
|
|
428
|
+
for (let index = 0; index < previous.length; index += 1) {
|
|
429
|
+
if (previous[index] !== next[index]) {
|
|
430
|
+
return false;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
return true;
|
|
434
|
+
}
|
|
261
435
|
function eventValue(event, currentValue) {
|
|
262
436
|
const target = event.currentTarget ?? event.target;
|
|
263
437
|
if (target !== null && typeof target === "object") {
|
|
@@ -303,6 +477,32 @@ function normalizeFieldErrors(errors) {
|
|
|
303
477
|
}
|
|
304
478
|
return typeof errors === "string" ? [errors] : [...errors];
|
|
305
479
|
}
|
|
480
|
+
function clampIndex(index, min, max) {
|
|
481
|
+
return Math.min(Math.max(Math.trunc(index), min), max);
|
|
482
|
+
}
|
|
483
|
+
function isArrayIndex(index, length) {
|
|
484
|
+
return Number.isInteger(index) && index >= 0 && index < length;
|
|
485
|
+
}
|
|
486
|
+
function validatorForField(entry) {
|
|
487
|
+
return typeof entry === "function" ? entry : entry?.validate;
|
|
488
|
+
}
|
|
489
|
+
function buildDependentFields(validate) {
|
|
490
|
+
const dependents = new Map();
|
|
491
|
+
for (const [fieldName, entry] of Object.entries(validate ?? {})) {
|
|
492
|
+
if (entry === undefined || typeof entry === "function") {
|
|
493
|
+
continue;
|
|
494
|
+
}
|
|
495
|
+
for (const dep of entry.deps ?? []) {
|
|
496
|
+
const fields = dependents.get(dep);
|
|
497
|
+
if (fields === undefined) {
|
|
498
|
+
dependents.set(dep, [fieldName]);
|
|
499
|
+
continue;
|
|
500
|
+
}
|
|
501
|
+
fields.push(fieldName);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
return dependents;
|
|
505
|
+
}
|
|
306
506
|
function cloneValues(values) {
|
|
307
507
|
return { ...values };
|
|
308
508
|
}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["import { cell, type ReadonlyCell } from \"@reckona/mreact-reactive-core\";\nimport { type StandardSchemaV1, validateStandardSchema } from \"./standard-schema.js\";\n\n/** Represents the object shape managed by a form instance. */\nexport type FormValues = Record<string, unknown>;\n\n/** Extracts string field names from a form value object. */\nexport type FieldName<TValues extends FormValues> = Extract<keyof TValues, string>;\n\n/** Maps field names and the root form key to validation error messages. */\nexport type FormErrors<TValues extends FormValues> = Partial<\n Record<FieldName<TValues> | \"root\", string[]>\n>;\n\n/** Validates one field value against the full form values object. */\nexport type FieldValidator<TValue, TValues extends FormValues> = (\n value: TValue,\n values: TValues,\n) => readonly string[] | string | undefined | Promise<readonly string[] | string | undefined>;\n\n/** Names the form events that can trigger validation. */\nexport type FormValidateMode = \"change\" | \"blur\" | \"submit\";\n\n/** Describes the complete reactive state tracked by a form instance. */\nexport interface FormState<TValues extends FormValues> {\n dirty: boolean;\n errors: FormErrors<TValues>;\n initialValues: TValues;\n submitCount: number;\n submitting: boolean;\n touched: Partial<Record<FieldName<TValues>, boolean>>;\n valid: boolean;\n validating: Partial<Record<FieldName<TValues>, boolean>>;\n values: TValues;\n}\n\n/** Describes the derived state for one form field. */\nexport interface FieldState<TValue> {\n dirty: boolean;\n errors: string[];\n touched: boolean;\n validating: boolean;\n value: TValue;\n}\n\n/** Provides DOM event handlers and current value for binding a field to an input. */\nexport interface FieldBinding<TValue> {\n onBlur(event: Event): Promise<void>;\n onChange(event: Event): Promise<void>;\n onInput(event: Event): Promise<void>;\n value: TValue;\n}\n\n/** Configures which DOM event updates a field binding. */\nexport interface FieldBindingOptions {\n event?: \"change\" | \"input\" | undefined;\n}\n\n/** Exposes state, binding, blur, and value update controls for one field. */\nexport interface FieldApi<TValues extends FormValues, Name extends FieldName<TValues>> {\n readonly state: ReadonlyCell<FieldState<TValues[Name]>>;\n bind(options?: FieldBindingOptions): FieldBinding<TValues[Name]>;\n blur(): Promise<void>;\n setValue(value: TValues[Name]): Promise<void>;\n}\n\ninterface BaseCreateFormOptions<TValues extends FormValues> {\n initialValues: TValues;\n validate?:\n | Partial<{\n [Name in FieldName<TValues>]: FieldValidator<TValues[Name], TValues>;\n }>\n | undefined;\n validateOn?: FormValidateMode | readonly FormValidateMode[] | undefined;\n}\n\n/** Configures form creation without schema-level submit value transformation. */\nexport interface CreateFormOptionsWithoutSchema<TValues extends FormValues>\n extends BaseCreateFormOptions<TValues> {\n schema?: undefined;\n}\n\n/** Configures form creation with a Standard Schema validator that may transform submit values. */\nexport interface CreateFormOptionsWithSchema<TValues extends FormValues, TSubmitValues>\n extends BaseCreateFormOptions<TValues> {\n schema: StandardSchemaV1<TValues, TSubmitValues>;\n}\n\n/** Configures form creation with optional field validators and optional Standard Schema validation. */\nexport type CreateFormOptions<TValues extends FormValues, TSubmitValues = TValues> =\n | CreateFormOptionsWithoutSchema<TValues>\n | CreateFormOptionsWithSchema<TValues, TSubmitValues>;\n\n/** Reports either successful normalized submit values or form validation errors. */\nexport type FormValidationResult<TValues extends FormValues, TSubmitValues> =\n | {\n success: true;\n value: TSubmitValues;\n }\n | {\n errors: FormErrors<TValues>;\n success: false;\n };\n\n/** Reports the result of a form submit handler after validation and error capture. */\nexport type FormSubmitResult<TValues extends FormValues, TResult> =\n | {\n data: TResult;\n status: \"success\";\n }\n | {\n status: \"duplicate\";\n }\n | {\n errors: FormErrors<TValues>;\n status: \"invalid\";\n }\n | {\n error: unknown;\n status: \"error\";\n };\n\n/** Represents field and form errors returned by a server action. */\nexport interface ServerActionErrors<TValues extends FormValues> {\n fieldErrors?: Partial<Record<FieldName<TValues>, readonly string[]>> | undefined;\n formErrors?: readonly string[] | undefined;\n}\n\n/** Provides reactive form state, field access, validation, submit, reset, and error controls. */\nexport interface FormApi<TValues extends FormValues, TSubmitValues> {\n readonly state: ReadonlyCell<FormState<TValues>>;\n field<Name extends FieldName<TValues>>(name: Name): FieldApi<TValues, Name>;\n getValues(): TValues;\n reset(values?: TValues): void;\n setErrors(errors: FormErrors<TValues>): void;\n setServerErrors(errors: ServerActionErrors<TValues>): void;\n setValue<Name extends FieldName<TValues>>(name: Name, value: TValues[Name]): Promise<void>;\n submit<TResult>(\n handler: (values: TSubmitValues) => Promise<TResult> | TResult,\n ): Promise<FormSubmitResult<TValues, TResult>>;\n validate(): Promise<FormValidationResult<TValues, TSubmitValues>>;\n}\n\n/** Creates a reactive form API from initial values, validators, and optional schema validation. */\nexport function createForm<TValues extends FormValues>(\n options: CreateFormOptionsWithoutSchema<TValues>,\n): FormApi<TValues, TValues>;\nexport function createForm<TValues extends FormValues, TSubmitValues>(\n options: CreateFormOptionsWithSchema<TValues, TSubmitValues>,\n): FormApi<TValues, TSubmitValues>;\nexport function createForm<TValues extends FormValues, TSubmitValues = TValues>(\n options: CreateFormOptions<TValues, TSubmitValues>,\n): FormApi<TValues, TSubmitValues> {\n const validateOn = new Set(\n Array.isArray(options.validateOn) ? options.validateOn : [options.validateOn ?? \"submit\"],\n );\n let initialValues = cloneValues(options.initialValues);\n const state = cell<FormState<TValues>>({\n dirty: false,\n errors: {},\n initialValues,\n submitCount: 0,\n submitting: false,\n touched: {},\n valid: true,\n validating: {},\n values: cloneValues(options.initialValues),\n });\n const validationGenerations = new Map<FieldName<TValues>, number>();\n const dirtyFields = new Set<FieldName<TValues>>();\n let activeSubmit: Promise<FormSubmitResult<TValues, unknown>> | undefined;\n\n function commit(patch: Partial<FormState<TValues>>, dirty = dirtyFields.size > 0): void {\n const previous = state.get();\n const next = {\n ...previous,\n ...patch,\n };\n next.dirty = dirty;\n next.valid = hasNoErrors(next.errors);\n state.set(next);\n }\n\n function setErrors(errors: FormErrors<TValues>): void {\n commit({ errors: normalizeErrors(errors) });\n }\n\n async function validateField<Name extends FieldName<TValues>>(name: Name): Promise<void> {\n const validator = options.validate?.[name];\n const generation = (validationGenerations.get(name) ?? 0) + 1;\n validationGenerations.set(name, generation);\n\n if (validator === undefined) {\n setFieldErrors(name, []);\n setFieldValidating(name, false);\n return;\n }\n\n const values = state.get().values;\n setFieldValidating(name, true);\n const errors = await validator(values[name], values);\n\n if (validationGenerations.get(name) !== generation) {\n return;\n }\n\n setFieldErrors(name, normalizeFieldErrors(errors));\n setFieldValidating(name, false);\n }\n\n function setFieldErrors<Name extends FieldName<TValues>>(\n name: Name,\n errors: readonly string[],\n ): void {\n const current = state.get().errors;\n setErrors({\n ...current,\n [name]: [...errors],\n } as FormErrors<TValues>);\n }\n\n function setFieldValidating<Name extends FieldName<TValues>>(\n name: Name,\n validating: boolean,\n ): void {\n const current = state.get().validating;\n commit({\n validating: {\n ...current,\n [name]: validating,\n },\n });\n }\n\n async function setValue<Name extends FieldName<TValues>>(\n name: Name,\n value: TValues[Name],\n ): Promise<void> {\n const previous = state.get();\n updateDirtyField(name, value);\n commit({\n values: {\n ...previous.values,\n [name]: value,\n },\n });\n\n if (validateOn.has(\"change\")) {\n await validateField(name);\n }\n }\n\n async function blurField<Name extends FieldName<TValues>>(name: Name): Promise<void> {\n commit({\n touched: {\n ...state.get().touched,\n [name]: true,\n },\n });\n\n if (validateOn.has(\"blur\")) {\n await validateField(name);\n }\n }\n\n async function validateForm(): Promise<FormValidationResult<TValues, TSubmitValues>> {\n const values = state.get().values;\n const errors: FormErrors<TValues> = {};\n\n for (const name of Object.keys(options.validate ?? {}) as Array<FieldName<TValues>>) {\n const validator = options.validate?.[name];\n if (validator === undefined) {\n continue;\n }\n const fieldErrors = normalizeFieldErrors(await validator(values[name], values));\n if (fieldErrors.length > 0) {\n errors[name] = fieldErrors;\n }\n }\n\n if (options.schema !== undefined) {\n const result = await validateStandardSchema(options.schema, values);\n\n if (!result.success) {\n mergeIssueErrors(errors, result.issues);\n } else if (Object.keys(errors).length === 0) {\n setErrors({});\n return {\n success: true,\n value: result.value as TSubmitValues,\n };\n }\n }\n\n if (Object.keys(errors).length > 0) {\n setErrors(errors);\n return {\n errors,\n success: false,\n };\n }\n\n setErrors({});\n return {\n success: true,\n value: values as TValues & TSubmitValues,\n };\n }\n\n return {\n state,\n field<Name extends FieldName<TValues>>(name: Name): FieldApi<TValues, Name> {\n return {\n state: {\n get: () => fieldState(state.get(), name),\n },\n bind(bindingOptions = {}) {\n const bindingEvent = bindingOptions.event ?? \"input\";\n const updateValue = async (inputEvent: Event) => {\n await setValue(name, eventValue(inputEvent, state.get().values[name]) as TValues[Name]);\n };\n\n return {\n onBlur: async () => {\n await blurField(name);\n },\n onChange: async (event) => {\n if (bindingEvent === \"change\") {\n await updateValue(event);\n }\n },\n onInput: async (event) => {\n if (bindingEvent === \"input\") {\n await updateValue(event);\n }\n },\n get value() {\n return state.get().values[name];\n },\n };\n },\n async blur() {\n await blurField(name);\n },\n setValue: (value) => setValue(name, value),\n };\n },\n getValues() {\n return state.get().values;\n },\n reset(values = initialValues): void {\n for (const name of Object.keys(options.validate ?? {}) as Array<FieldName<TValues>>) {\n validationGenerations.set(name, (validationGenerations.get(name) ?? 0) + 1);\n }\n\n initialValues = cloneValues(values);\n dirtyFields.clear();\n commit({\n errors: {},\n initialValues,\n submitCount: 0,\n submitting: false,\n touched: {},\n validating: {},\n values: cloneValues(values),\n });\n },\n setErrors,\n setServerErrors(errors): void {\n const next: FormErrors<TValues> = {};\n\n for (const [name, messages] of Object.entries(errors.fieldErrors ?? {})) {\n if (isDangerousObjectKey(name)) {\n continue;\n }\n\n if (messages !== undefined && messages.length > 0) {\n next[name as FieldName<TValues>] = [...messages];\n }\n }\n\n if (errors.formErrors !== undefined && errors.formErrors.length > 0) {\n next.root = [...errors.formErrors];\n }\n\n setErrors(next);\n },\n setValue,\n async submit<TResult>(\n handler: (values: TSubmitValues) => Promise<TResult> | TResult,\n ): Promise<FormSubmitResult<TValues, TResult>> {\n if (activeSubmit !== undefined) {\n return { status: \"duplicate\" };\n }\n\n const task = (async (): Promise<FormSubmitResult<TValues, TResult>> => {\n commit({\n submitCount: state.get().submitCount + 1,\n submitting: true,\n });\n\n try {\n const validation = await validateForm();\n\n if (!validation.success) {\n return {\n errors: validation.errors,\n status: \"invalid\",\n };\n }\n\n try {\n const data = await handler(validation.value);\n return {\n data,\n status: \"success\",\n };\n } catch (error) {\n return {\n error,\n status: \"error\",\n };\n }\n } finally {\n activeSubmit = undefined;\n commit({ submitting: false });\n }\n })();\n\n activeSubmit = task as Promise<FormSubmitResult<TValues, unknown>>;\n return task;\n },\n validate: validateForm,\n };\n\n function updateDirtyField<Name extends FieldName<TValues>>(\n name: Name,\n value: TValues[Name],\n ): void {\n if (Object.is(value, initialValues[name])) {\n dirtyFields.delete(name);\n return;\n }\n\n dirtyFields.add(name);\n }\n}\n\nfunction fieldState<TValues extends FormValues, Name extends FieldName<TValues>>(\n formState: FormState<TValues>,\n name: Name,\n): FieldState<TValues[Name]> {\n return {\n dirty: !Object.is(formState.values[name], formState.initialValues[name]),\n errors: [...(formState.errors[name] ?? [])],\n touched: formState.touched[name] === true,\n validating: formState.validating[name] === true,\n value: formState.values[name],\n };\n}\n\nfunction eventValue(event: Event, currentValue: unknown): unknown {\n const target = event.currentTarget ?? event.target;\n\n if (target !== null && typeof target === \"object\") {\n if (typeof currentValue === \"boolean\" && \"checked\" in target) {\n return Boolean((target as { checked: unknown }).checked);\n }\n\n if (\"value\" in target) {\n return (target as { value: unknown }).value;\n }\n }\n\n return currentValue;\n}\n\nfunction mergeIssueErrors<TValues extends FormValues>(\n errors: FormErrors<TValues>,\n issues: ReadonlyArray<StandardSchemaV1.Issue>,\n): void {\n for (const issue of issues) {\n const key = issuePathKey(issue.path);\n const name = (key === undefined || isDangerousObjectKey(key) ? \"root\" : key) as\n | FieldName<TValues>\n | \"root\";\n const messages = errors[name] ?? [];\n messages.push(issue.message);\n errors[name] = messages;\n }\n}\n\nfunction issuePathKey(\n path: ReadonlyArray<PropertyKey | StandardSchemaV1.PathSegment> | undefined,\n): string | undefined {\n const first = path?.[0];\n\n if (first === undefined) {\n return undefined;\n }\n\n return typeof first === \"object\" && first !== null && \"key\" in first\n ? String(first.key)\n : String(first);\n}\n\nfunction normalizeErrors<TValues extends FormValues>(\n errors: FormErrors<TValues>,\n): FormErrors<TValues> {\n return Object.fromEntries(\n Object.entries(errors).flatMap(([name, messages]) =>\n messages === undefined || messages.length === 0 ? [] : [[name, [...messages]]],\n ),\n ) as FormErrors<TValues>;\n}\n\nfunction isDangerousObjectKey(key: PropertyKey): boolean {\n return key === \"__proto__\" || key === \"constructor\" || key === \"prototype\";\n}\n\nfunction hasNoErrors<TValues extends FormValues>(errors: FormErrors<TValues>): boolean {\n return (Object.values(errors) as Array<string[] | undefined>).every(\n (messages) => messages === undefined || messages.length === 0,\n );\n}\n\nfunction normalizeFieldErrors(errors: readonly string[] | string | undefined): string[] {\n if (errors === undefined) {\n return [];\n }\n\n return typeof errors === \"string\" ? [errors] : [...errors];\n}\n\nfunction cloneValues<TValues extends FormValues>(values: TValues): TValues {\n return { ...values };\n}\n\n/** Re-exports Standard Schema types used by form schema options. */\nexport type {\n InferStandardSchemaInput,\n InferStandardSchemaOutput,\n StandardSchemaV1,\n} from \"./standard-schema.js\";\n/** Validates an unknown value with a Standard Schema and normalizes the result shape. */\nexport { validateStandardSchema } from \"./standard-schema.js\";\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["import { cell, computed, type ReadonlyCell } from \"@reckona/mreact-reactive-core\";\nimport { type StandardSchemaV1, validateStandardSchema } from \"./standard-schema.js\";\n\n/** Represents the object shape managed by a form instance. */\nexport type FormValues = Record<string, unknown>;\n\n/** Extracts string field names from a form value object. */\nexport type FieldName<TValues extends FormValues> = Extract<keyof TValues, string>;\n\n/** Maps field names and the root form key to validation error messages. */\nexport type FormErrors<TValues extends FormValues> = Partial<\n Record<FieldName<TValues> | \"root\", string[]>\n>;\n\n/** Validates one field value against the full form values object. */\nexport type FieldValidator<TValue, TValues extends FormValues> = (\n value: TValue,\n values: TValues,\n) => readonly string[] | string | undefined | Promise<readonly string[] | string | undefined>;\n\n/** Configures one field validator and which other fields should re-trigger it. */\nexport interface FieldValidationConfig<TValue, TValues extends FormValues> {\n deps?: readonly FieldName<TValues>[] | undefined;\n validate: FieldValidator<TValue, TValues>;\n}\n\n/** Provides either a validator function or a validator descriptor with dependencies. */\nexport type FieldValidationEntry<TValue, TValues extends FormValues> =\n | FieldValidator<TValue, TValues>\n | FieldValidationConfig<TValue, TValues>;\n\n/** Names the form events that can trigger validation. */\nexport type FormValidateMode = \"change\" | \"blur\" | \"submit\";\n\n/** Describes the complete reactive state tracked by a form instance. */\nexport interface FormState<TValues extends FormValues> {\n dirty: boolean;\n errors: FormErrors<TValues>;\n initialValues: TValues;\n submitCount: number;\n submitting: boolean;\n touched: Partial<Record<FieldName<TValues>, boolean>>;\n valid: boolean;\n validating: Partial<Record<FieldName<TValues>, boolean>>;\n values: TValues;\n}\n\n/** Describes the derived state for one form field. */\nexport interface FieldState<TValue> {\n dirty: boolean;\n errors: string[];\n touched: boolean;\n validating: boolean;\n value: TValue;\n}\n\n/** Provides DOM event handlers and current value for binding a field to an input. */\nexport interface FieldBinding<TValue> {\n onBlur(event: Event): Promise<void>;\n onChange(event: Event): Promise<void>;\n onInput(event: Event): Promise<void>;\n value: TValue;\n}\n\n/** Configures which DOM event updates a field binding. */\nexport interface FieldBindingOptions {\n event?: \"change\" | \"input\" | undefined;\n}\n\n/** Exposes state, binding, blur, and value update controls for one field. */\nexport interface FieldApi<TValues extends FormValues, Name extends FieldName<TValues>> {\n readonly state: ReadonlyCell<FieldState<TValues[Name]>>;\n bind(options?: FieldBindingOptions): FieldBinding<TValues[Name]>;\n blur(): Promise<void>;\n setValue(value: TValues[Name]): Promise<void>;\n}\n\n/** Extracts an array field's item type. */\nexport type ArrayFieldValue<\n TValues extends FormValues,\n Name extends FieldName<TValues>,\n> = TValues[Name] extends readonly (infer Item)[] ? Item : never;\n\n/** Describes one rendered row in a form array field. */\nexport interface FieldArrayRow<TValue> {\n index: number;\n key: string;\n value: TValue;\n}\n\n/** Exposes stable keyed row state and mutation helpers for an array field. */\nexport interface FieldArrayApi<TValues extends FormValues, Name extends FieldName<TValues>> {\n readonly fields: ReadonlyCell<Array<FieldArrayRow<ArrayFieldValue<TValues, Name>>>>;\n append(value: ArrayFieldValue<TValues, Name>): Promise<void>;\n insert(index: number, value: ArrayFieldValue<TValues, Name>): Promise<void>;\n move(from: number, to: number): Promise<void>;\n remove(index: number): Promise<void>;\n swap(first: number, second: number): Promise<void>;\n}\n\ninterface BaseCreateFormOptions<TValues extends FormValues> {\n initialValues: TValues;\n validate?:\n | Partial<{\n [Name in FieldName<TValues>]: FieldValidationEntry<TValues[Name], TValues>;\n }>\n | undefined;\n validateOn?: FormValidateMode | readonly FormValidateMode[] | undefined;\n}\n\n/** Configures form creation without schema-level submit value transformation. */\nexport interface CreateFormOptionsWithoutSchema<\n TValues extends FormValues,\n> extends BaseCreateFormOptions<TValues> {\n schema?: undefined;\n}\n\n/** Configures form creation with a Standard Schema validator that may transform submit values. */\nexport interface CreateFormOptionsWithSchema<\n TValues extends FormValues,\n TSubmitValues,\n> extends BaseCreateFormOptions<TValues> {\n schema: StandardSchemaV1<TValues, TSubmitValues>;\n}\n\n/** Configures form creation with optional field validators and optional Standard Schema validation. */\nexport type CreateFormOptions<TValues extends FormValues, TSubmitValues = TValues> =\n | CreateFormOptionsWithoutSchema<TValues>\n | CreateFormOptionsWithSchema<TValues, TSubmitValues>;\n\n/** Reports either successful normalized submit values or form validation errors. */\nexport type FormValidationResult<TValues extends FormValues, TSubmitValues> =\n | {\n success: true;\n value: TSubmitValues;\n }\n | {\n errors: FormErrors<TValues>;\n success: false;\n };\n\n/** Reports the result of a form submit handler after validation and error capture. */\nexport type FormSubmitResult<TValues extends FormValues, TResult> =\n | {\n data: TResult;\n status: \"success\";\n }\n | {\n status: \"duplicate\";\n }\n | {\n errors: FormErrors<TValues>;\n status: \"invalid\";\n }\n | {\n error: unknown;\n status: \"error\";\n };\n\n/** Represents field and form errors returned by a server action. */\nexport interface ServerActionErrors<TValues extends FormValues> {\n fieldErrors?: Partial<Record<FieldName<TValues>, readonly string[]>> | undefined;\n formErrors?: readonly string[] | undefined;\n}\n\n/** Provides reactive form state, field access, validation, submit, reset, and error controls. */\nexport interface FormApi<TValues extends FormValues, TSubmitValues> {\n readonly state: ReadonlyCell<FormState<TValues>>;\n fieldArray<Name extends FieldName<TValues>>(name: Name): FieldArrayApi<TValues, Name>;\n field<Name extends FieldName<TValues>>(name: Name): FieldApi<TValues, Name>;\n getValues(): TValues;\n reset(values?: TValues): void;\n setErrors(errors: FormErrors<TValues>): void;\n setServerErrors(errors: ServerActionErrors<TValues>): void;\n setValue<Name extends FieldName<TValues>>(name: Name, value: TValues[Name]): Promise<void>;\n submit<TResult>(\n handler: (values: TSubmitValues) => Promise<TResult> | TResult,\n ): Promise<FormSubmitResult<TValues, TResult>>;\n validate(): Promise<FormValidationResult<TValues, TSubmitValues>>;\n}\n\n/** Creates a reactive form API from initial values, validators, and optional schema validation. */\nexport function createForm<TValues extends FormValues>(\n options: CreateFormOptionsWithoutSchema<TValues>,\n): FormApi<TValues, TValues>;\nexport function createForm<TValues extends FormValues, TSubmitValues>(\n options: CreateFormOptionsWithSchema<TValues, TSubmitValues>,\n): FormApi<TValues, TSubmitValues>;\nexport function createForm<TValues extends FormValues, TSubmitValues = TValues>(\n options: CreateFormOptions<TValues, TSubmitValues>,\n): FormApi<TValues, TSubmitValues> {\n const validateOn = new Set(\n Array.isArray(options.validateOn) ? options.validateOn : [options.validateOn ?? \"submit\"],\n );\n let initialValues = cloneValues(options.initialValues);\n const state = cell<FormState<TValues>>({\n dirty: false,\n errors: {},\n initialValues,\n submitCount: 0,\n submitting: false,\n touched: {},\n valid: true,\n validating: {},\n values: cloneValues(options.initialValues),\n });\n const validationGenerations = new Map<FieldName<TValues>, number>();\n const dirtyFields = new Set<FieldName<TValues>>();\n const dependentFields = buildDependentFields(options.validate);\n const fieldArrayKeys = new Map<FieldName<TValues>, string[]>();\n const fieldStateCells = new Map<\n FieldName<TValues>,\n ReadonlyCell<FieldState<TValues[FieldName<TValues>]>>\n >();\n const fieldArrayCells = new Map<\n FieldName<TValues>,\n ReadonlyCell<Array<FieldArrayRow<ArrayFieldValue<TValues, FieldName<TValues>>>>>\n >();\n let nextFieldArrayKey = 0;\n let activeSubmit: Promise<FormSubmitResult<TValues, unknown>> | undefined;\n\n function commit(patch: Partial<FormState<TValues>>, dirty = dirtyFields.size > 0): void {\n const previous = state.get();\n const next = {\n ...previous,\n ...patch,\n };\n next.dirty = dirty;\n next.valid = hasNoErrors(next.errors);\n state.set(next);\n }\n\n function setErrors(errors: FormErrors<TValues>): void {\n commit({ errors: normalizeErrors(errors) });\n }\n\n function invalidateFieldValidations(names: readonly FieldName<TValues>[]): void {\n if (names.length === 0) {\n return;\n }\n\n const validating = { ...state.get().validating };\n for (const name of names) {\n validationGenerations.set(name, (validationGenerations.get(name) ?? 0) + 1);\n validating[name] = false;\n }\n commit({ validating });\n }\n\n async function validateField<Name extends FieldName<TValues>>(name: Name): Promise<void> {\n const validator = validatorForField(options.validate?.[name]);\n const generation = (validationGenerations.get(name) ?? 0) + 1;\n validationGenerations.set(name, generation);\n\n if (validator === undefined) {\n setFieldErrors(name, []);\n setFieldValidating(name, false);\n return;\n }\n\n const values = state.get().values;\n setFieldValidating(name, true);\n const errors = await validator(values[name], values);\n\n if (validationGenerations.get(name) !== generation) {\n return;\n }\n\n setFieldErrors(name, normalizeFieldErrors(errors));\n setFieldValidating(name, false);\n }\n\n function setFieldErrors<Name extends FieldName<TValues>>(\n name: Name,\n errors: readonly string[],\n ): void {\n const current = state.get().errors;\n setErrors({\n ...current,\n [name]: [...errors],\n } as FormErrors<TValues>);\n }\n\n function setFieldValidating<Name extends FieldName<TValues>>(\n name: Name,\n validating: boolean,\n ): void {\n const current = state.get().validating;\n commit({\n validating: {\n ...current,\n [name]: validating,\n },\n });\n }\n\n async function setValue<Name extends FieldName<TValues>>(\n name: Name,\n value: TValues[Name],\n ): Promise<void> {\n const previous = state.get();\n updateDirtyField(name, value);\n commit({\n values: {\n ...previous.values,\n [name]: value,\n },\n });\n\n if (validateOn.has(\"change\")) {\n await validateFields([name, ...dependentFieldsFor(name)]);\n }\n }\n\n async function blurField<Name extends FieldName<TValues>>(name: Name): Promise<void> {\n commit({\n touched: {\n ...state.get().touched,\n [name]: true,\n },\n });\n\n if (validateOn.has(\"blur\")) {\n await validateFields([name, ...dependentFieldsFor(name)]);\n }\n }\n\n async function validateForm(): Promise<FormValidationResult<TValues, TSubmitValues>> {\n const values = state.get().values;\n const errors: FormErrors<TValues> = {};\n const fieldNames = Object.keys(options.validate ?? {}) as Array<FieldName<TValues>>;\n invalidateFieldValidations(fieldNames);\n\n for (const name of fieldNames) {\n const validator = options.validate?.[name];\n const validate = validatorForField(validator);\n if (validate === undefined) {\n continue;\n }\n const fieldErrors = normalizeFieldErrors(await validate(values[name], values));\n if (fieldErrors.length > 0) {\n errors[name] = fieldErrors;\n }\n }\n\n if (options.schema !== undefined) {\n const result = await validateStandardSchema(options.schema, values);\n\n if (!result.success) {\n mergeIssueErrors(errors, result.issues);\n } else if (Object.keys(errors).length === 0) {\n setErrors({});\n return {\n success: true,\n value: result.value as TSubmitValues,\n };\n }\n }\n\n if (Object.keys(errors).length > 0) {\n setErrors(errors);\n return {\n errors,\n success: false,\n };\n }\n\n setErrors({});\n return {\n success: true,\n value: values as TValues & TSubmitValues,\n };\n }\n\n async function validateFields(names: readonly FieldName<TValues>[]): Promise<void> {\n const uniqueNames = [...new Set(names)];\n await Promise.all(uniqueNames.map((fieldName) => validateField(fieldName)));\n }\n\n function dependentFieldsFor(name: FieldName<TValues>): readonly FieldName<TValues>[] {\n return dependentFields.get(name) ?? [];\n }\n\n function arrayValues<Name extends FieldName<TValues>>(\n name: Name,\n ): Array<ArrayFieldValue<TValues, Name>> {\n const value = state.get().values[name];\n return Array.isArray(value) ? ([...value] as Array<ArrayFieldValue<TValues, Name>>) : [];\n }\n\n function createFieldArrayKey(name: FieldName<TValues>): string {\n const key = `${name}:${nextFieldArrayKey}`;\n nextFieldArrayKey += 1;\n return key;\n }\n\n function ensureFieldArrayKeys(name: FieldName<TValues>, length: number): string[] {\n const keys = fieldArrayKeys.get(name) ?? [];\n if (keys.length > length) {\n keys.length = length;\n }\n\n while (keys.length < length) {\n keys.push(createFieldArrayKey(name));\n }\n\n fieldArrayKeys.set(name, keys);\n return keys;\n }\n\n function fieldArrayRows<Name extends FieldName<TValues>>(\n name: Name,\n ): Array<FieldArrayRow<ArrayFieldValue<TValues, Name>>> {\n const values = arrayValues(name);\n const keys = ensureFieldArrayKeys(name, values.length);\n return values.map((value, index) => ({\n index,\n key: keys[index] ?? createFieldArrayKey(name),\n value,\n }));\n }\n\n function fieldArrayCell<Name extends FieldName<TValues>>(\n name: Name,\n ): ReadonlyCell<Array<FieldArrayRow<ArrayFieldValue<TValues, Name>>>> {\n const existing = fieldArrayCells.get(name);\n if (existing !== undefined) {\n return existing as ReadonlyCell<Array<FieldArrayRow<ArrayFieldValue<TValues, Name>>>>;\n }\n\n const next = computed(() => fieldArrayRows(name), { equals: fieldArrayRowsEqual });\n fieldArrayCells.set(\n name,\n next as ReadonlyCell<Array<FieldArrayRow<ArrayFieldValue<TValues, FieldName<TValues>>>>>,\n );\n return next;\n }\n\n function fieldStateCell<Name extends FieldName<TValues>>(\n name: Name,\n ): ReadonlyCell<FieldState<TValues[Name]>> {\n const existing = fieldStateCells.get(name);\n if (existing !== undefined) {\n return existing as ReadonlyCell<FieldState<TValues[Name]>>;\n }\n\n const next = computed(() => fieldState(state.get(), name), { equals: fieldStateEquals });\n fieldStateCells.set(name, next as ReadonlyCell<FieldState<TValues[FieldName<TValues>]>>);\n return next;\n }\n\n async function setArrayValue<Name extends FieldName<TValues>>(\n name: Name,\n values: Array<ArrayFieldValue<TValues, Name>>,\n keys: string[],\n ): Promise<void> {\n fieldArrayKeys.set(name, keys);\n await setValue(name, values as TValues[Name]);\n }\n\n async function validateArrayField<Name extends FieldName<TValues>>(name: Name): Promise<void> {\n if (validateOn.has(\"change\")) {\n await validateFields([name, ...dependentFieldsFor(name)]);\n }\n }\n\n return {\n state,\n fieldArray<Name extends FieldName<TValues>>(name: Name): FieldArrayApi<TValues, Name> {\n return {\n fields: fieldArrayCell(name),\n async append(value) {\n const values = arrayValues(name);\n const keys = [...ensureFieldArrayKeys(name, values.length), createFieldArrayKey(name)];\n values.push(value);\n await setArrayValue(name, values, keys);\n },\n async insert(index, value) {\n const values = arrayValues(name);\n const insertIndex = clampIndex(index, 0, values.length);\n const keys = [...ensureFieldArrayKeys(name, values.length)];\n values.splice(insertIndex, 0, value);\n keys.splice(insertIndex, 0, createFieldArrayKey(name));\n await setArrayValue(name, values, keys);\n },\n async move(from, to) {\n const values = arrayValues(name);\n const keys = [...ensureFieldArrayKeys(name, values.length)];\n if (!isArrayIndex(from, values.length)) {\n await validateArrayField(name);\n return;\n }\n\n const [value] = values.splice(from, 1);\n const [key] = keys.splice(from, 1);\n const insertIndex = clampIndex(to, 0, values.length);\n values.splice(insertIndex, 0, value as ArrayFieldValue<TValues, Name>);\n keys.splice(insertIndex, 0, key as string);\n await setArrayValue(name, values, keys);\n },\n async remove(index) {\n const values = arrayValues(name);\n const keys = [...ensureFieldArrayKeys(name, values.length)];\n if (!isArrayIndex(index, values.length)) {\n await validateArrayField(name);\n return;\n }\n\n values.splice(index, 1);\n keys.splice(index, 1);\n await setArrayValue(name, values, keys);\n },\n async swap(first, second) {\n const values = arrayValues(name);\n const keys = [...ensureFieldArrayKeys(name, values.length)];\n if (!isArrayIndex(first, values.length) || !isArrayIndex(second, values.length)) {\n await validateArrayField(name);\n return;\n }\n\n [values[first], values[second]] = [\n values[second] as ArrayFieldValue<TValues, Name>,\n values[first] as ArrayFieldValue<TValues, Name>,\n ];\n [keys[first], keys[second]] = [keys[second] as string, keys[first] as string];\n await setArrayValue(name, values, keys);\n },\n };\n },\n field<Name extends FieldName<TValues>>(name: Name): FieldApi<TValues, Name> {\n return {\n state: fieldStateCell(name),\n bind(bindingOptions = {}) {\n const bindingEvent = bindingOptions.event ?? \"input\";\n const updateValue = async (inputEvent: Event) => {\n await setValue(name, eventValue(inputEvent, state.get().values[name]) as TValues[Name]);\n };\n\n return {\n onBlur: async () => {\n await blurField(name);\n },\n onChange: async (event) => {\n if (bindingEvent === \"change\") {\n await updateValue(event);\n }\n },\n onInput: async (event) => {\n if (bindingEvent === \"input\") {\n await updateValue(event);\n }\n },\n get value() {\n return state.get().values[name];\n },\n };\n },\n async blur() {\n await blurField(name);\n },\n setValue: (value) => setValue(name, value),\n };\n },\n getValues() {\n return state.get().values;\n },\n reset(values = initialValues): void {\n for (const name of Object.keys(options.validate ?? {}) as Array<FieldName<TValues>>) {\n validationGenerations.set(name, (validationGenerations.get(name) ?? 0) + 1);\n }\n\n initialValues = cloneValues(values);\n dirtyFields.clear();\n fieldArrayKeys.clear();\n commit({\n errors: {},\n initialValues,\n submitCount: 0,\n submitting: false,\n touched: {},\n validating: {},\n values: cloneValues(values),\n });\n },\n setErrors,\n setServerErrors(errors): void {\n const next: FormErrors<TValues> = {};\n\n for (const [name, messages] of Object.entries(errors.fieldErrors ?? {})) {\n if (isDangerousObjectKey(name)) {\n continue;\n }\n\n if (messages !== undefined && messages.length > 0) {\n next[name as FieldName<TValues>] = [...messages];\n }\n }\n\n if (errors.formErrors !== undefined && errors.formErrors.length > 0) {\n next.root = [...errors.formErrors];\n }\n\n setErrors(next);\n },\n setValue,\n async submit<TResult>(\n handler: (values: TSubmitValues) => Promise<TResult> | TResult,\n ): Promise<FormSubmitResult<TValues, TResult>> {\n if (activeSubmit !== undefined) {\n return { status: \"duplicate\" };\n }\n\n const task = (async (): Promise<FormSubmitResult<TValues, TResult>> => {\n commit({\n submitCount: state.get().submitCount + 1,\n submitting: true,\n });\n\n try {\n const validation = await validateForm();\n\n if (!validation.success) {\n return {\n errors: validation.errors,\n status: \"invalid\",\n };\n }\n\n try {\n const data = await handler(validation.value);\n return {\n data,\n status: \"success\",\n };\n } catch (error) {\n return {\n error,\n status: \"error\",\n };\n }\n } finally {\n activeSubmit = undefined;\n commit({ submitting: false });\n }\n })();\n\n activeSubmit = task as Promise<FormSubmitResult<TValues, unknown>>;\n return task;\n },\n validate: validateForm,\n };\n\n function updateDirtyField<Name extends FieldName<TValues>>(\n name: Name,\n value: TValues[Name],\n ): void {\n if (Object.is(value, initialValues[name])) {\n dirtyFields.delete(name);\n return;\n }\n\n dirtyFields.add(name);\n }\n}\n\nfunction fieldState<TValues extends FormValues, Name extends FieldName<TValues>>(\n formState: FormState<TValues>,\n name: Name,\n): FieldState<TValues[Name]> {\n return {\n dirty: !Object.is(formState.values[name], formState.initialValues[name]),\n errors: [...(formState.errors[name] ?? [])],\n touched: formState.touched[name] === true,\n validating: formState.validating[name] === true,\n value: formState.values[name],\n };\n}\n\nfunction fieldStateEquals<TValue>(previous: FieldState<TValue>, next: FieldState<TValue>): boolean {\n return (\n previous.dirty === next.dirty &&\n previous.touched === next.touched &&\n previous.validating === next.validating &&\n Object.is(previous.value, next.value) &&\n stringArraysEqual(previous.errors, next.errors)\n );\n}\n\nfunction fieldArrayRowsEqual<TValue>(\n previous: Array<FieldArrayRow<TValue>>,\n next: Array<FieldArrayRow<TValue>>,\n): boolean {\n if (previous.length !== next.length) {\n return false;\n }\n\n for (let index = 0; index < previous.length; index += 1) {\n const previousRow = previous[index];\n const nextRow = next[index];\n if (\n previousRow === undefined ||\n nextRow === undefined ||\n previousRow.index !== nextRow.index ||\n previousRow.key !== nextRow.key ||\n !Object.is(previousRow.value, nextRow.value)\n ) {\n return false;\n }\n }\n\n return true;\n}\n\nfunction stringArraysEqual(previous: readonly string[], next: readonly string[]): boolean {\n if (previous.length !== next.length) {\n return false;\n }\n\n for (let index = 0; index < previous.length; index += 1) {\n if (previous[index] !== next[index]) {\n return false;\n }\n }\n\n return true;\n}\n\nfunction eventValue(event: Event, currentValue: unknown): unknown {\n const target = event.currentTarget ?? event.target;\n\n if (target !== null && typeof target === \"object\") {\n if (typeof currentValue === \"boolean\" && \"checked\" in target) {\n return Boolean((target as { checked: unknown }).checked);\n }\n\n if (\"value\" in target) {\n return (target as { value: unknown }).value;\n }\n }\n\n return currentValue;\n}\n\nfunction mergeIssueErrors<TValues extends FormValues>(\n errors: FormErrors<TValues>,\n issues: ReadonlyArray<StandardSchemaV1.Issue>,\n): void {\n for (const issue of issues) {\n const key = issuePathKey(issue.path);\n const name = (key === undefined || isDangerousObjectKey(key) ? \"root\" : key) as\n | FieldName<TValues>\n | \"root\";\n const messages = errors[name] ?? [];\n messages.push(issue.message);\n errors[name] = messages;\n }\n}\n\nfunction issuePathKey(\n path: ReadonlyArray<PropertyKey | StandardSchemaV1.PathSegment> | undefined,\n): string | undefined {\n const first = path?.[0];\n\n if (first === undefined) {\n return undefined;\n }\n\n return typeof first === \"object\" && first !== null && \"key\" in first\n ? String(first.key)\n : String(first);\n}\n\nfunction normalizeErrors<TValues extends FormValues>(\n errors: FormErrors<TValues>,\n): FormErrors<TValues> {\n return Object.fromEntries(\n Object.entries(errors).flatMap(([name, messages]) =>\n messages === undefined || messages.length === 0 ? [] : [[name, [...messages]]],\n ),\n ) as FormErrors<TValues>;\n}\n\nfunction isDangerousObjectKey(key: PropertyKey): boolean {\n return key === \"__proto__\" || key === \"constructor\" || key === \"prototype\";\n}\n\nfunction hasNoErrors<TValues extends FormValues>(errors: FormErrors<TValues>): boolean {\n return (Object.values(errors) as Array<string[] | undefined>).every(\n (messages) => messages === undefined || messages.length === 0,\n );\n}\n\nfunction normalizeFieldErrors(errors: readonly string[] | string | undefined): string[] {\n if (errors === undefined) {\n return [];\n }\n\n return typeof errors === \"string\" ? [errors] : [...errors];\n}\n\nfunction clampIndex(index: number, min: number, max: number): number {\n return Math.min(Math.max(Math.trunc(index), min), max);\n}\n\nfunction isArrayIndex(index: number, length: number): boolean {\n return Number.isInteger(index) && index >= 0 && index < length;\n}\n\nfunction validatorForField<TValue, TValues extends FormValues>(\n entry: FieldValidationEntry<TValue, TValues> | undefined,\n): FieldValidator<TValue, TValues> | undefined {\n return typeof entry === \"function\" ? entry : entry?.validate;\n}\n\nfunction buildDependentFields<TValues extends FormValues>(\n validate: BaseCreateFormOptions<TValues>[\"validate\"],\n): Map<FieldName<TValues>, FieldName<TValues>[]> {\n const dependents = new Map<FieldName<TValues>, FieldName<TValues>[]>();\n\n for (const [fieldName, entry] of Object.entries(validate ?? {}) as Array<\n [FieldName<TValues>, FieldValidationEntry<TValues[FieldName<TValues>], TValues> | undefined]\n >) {\n if (entry === undefined || typeof entry === \"function\") {\n continue;\n }\n\n for (const dep of entry.deps ?? []) {\n const fields = dependents.get(dep);\n if (fields === undefined) {\n dependents.set(dep, [fieldName]);\n continue;\n }\n fields.push(fieldName);\n }\n }\n\n return dependents;\n}\n\nfunction cloneValues<TValues extends FormValues>(values: TValues): TValues {\n return { ...values };\n}\n\n/** Re-exports Standard Schema types used by form schema options. */\nexport type {\n InferStandardSchemaInput,\n InferStandardSchemaOutput,\n StandardSchemaV1,\n} from \"./standard-schema.js\";\n/** Validates an unknown value with a Standard Schema and normalizes the result shape. */\nexport { validateStandardSchema } from \"./standard-schema.js\";\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reckona/mreact-forms",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.183",
|
|
4
4
|
"description": "Reactive form state and validation utilities for mreact.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"forms",
|
|
@@ -44,6 +44,6 @@
|
|
|
44
44
|
"access": "public"
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@reckona/mreact-reactive-core": "0.0.
|
|
47
|
+
"@reckona/mreact-reactive-core": "0.0.183"
|
|
48
48
|
}
|
|
49
49
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { cell, type ReadonlyCell } from "@reckona/mreact-reactive-core";
|
|
1
|
+
import { cell, computed, type ReadonlyCell } from "@reckona/mreact-reactive-core";
|
|
2
2
|
import { type StandardSchemaV1, validateStandardSchema } from "./standard-schema.js";
|
|
3
3
|
|
|
4
4
|
/** Represents the object shape managed by a form instance. */
|
|
@@ -18,6 +18,17 @@ export type FieldValidator<TValue, TValues extends FormValues> = (
|
|
|
18
18
|
values: TValues,
|
|
19
19
|
) => readonly string[] | string | undefined | Promise<readonly string[] | string | undefined>;
|
|
20
20
|
|
|
21
|
+
/** Configures one field validator and which other fields should re-trigger it. */
|
|
22
|
+
export interface FieldValidationConfig<TValue, TValues extends FormValues> {
|
|
23
|
+
deps?: readonly FieldName<TValues>[] | undefined;
|
|
24
|
+
validate: FieldValidator<TValue, TValues>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Provides either a validator function or a validator descriptor with dependencies. */
|
|
28
|
+
export type FieldValidationEntry<TValue, TValues extends FormValues> =
|
|
29
|
+
| FieldValidator<TValue, TValues>
|
|
30
|
+
| FieldValidationConfig<TValue, TValues>;
|
|
31
|
+
|
|
21
32
|
/** Names the form events that can trigger validation. */
|
|
22
33
|
export type FormValidateMode = "change" | "blur" | "submit";
|
|
23
34
|
|
|
@@ -64,25 +75,51 @@ export interface FieldApi<TValues extends FormValues, Name extends FieldName<TVa
|
|
|
64
75
|
setValue(value: TValues[Name]): Promise<void>;
|
|
65
76
|
}
|
|
66
77
|
|
|
78
|
+
/** Extracts an array field's item type. */
|
|
79
|
+
export type ArrayFieldValue<
|
|
80
|
+
TValues extends FormValues,
|
|
81
|
+
Name extends FieldName<TValues>,
|
|
82
|
+
> = TValues[Name] extends readonly (infer Item)[] ? Item : never;
|
|
83
|
+
|
|
84
|
+
/** Describes one rendered row in a form array field. */
|
|
85
|
+
export interface FieldArrayRow<TValue> {
|
|
86
|
+
index: number;
|
|
87
|
+
key: string;
|
|
88
|
+
value: TValue;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Exposes stable keyed row state and mutation helpers for an array field. */
|
|
92
|
+
export interface FieldArrayApi<TValues extends FormValues, Name extends FieldName<TValues>> {
|
|
93
|
+
readonly fields: ReadonlyCell<Array<FieldArrayRow<ArrayFieldValue<TValues, Name>>>>;
|
|
94
|
+
append(value: ArrayFieldValue<TValues, Name>): Promise<void>;
|
|
95
|
+
insert(index: number, value: ArrayFieldValue<TValues, Name>): Promise<void>;
|
|
96
|
+
move(from: number, to: number): Promise<void>;
|
|
97
|
+
remove(index: number): Promise<void>;
|
|
98
|
+
swap(first: number, second: number): Promise<void>;
|
|
99
|
+
}
|
|
100
|
+
|
|
67
101
|
interface BaseCreateFormOptions<TValues extends FormValues> {
|
|
68
102
|
initialValues: TValues;
|
|
69
103
|
validate?:
|
|
70
104
|
| Partial<{
|
|
71
|
-
[Name in FieldName<TValues>]:
|
|
105
|
+
[Name in FieldName<TValues>]: FieldValidationEntry<TValues[Name], TValues>;
|
|
72
106
|
}>
|
|
73
107
|
| undefined;
|
|
74
108
|
validateOn?: FormValidateMode | readonly FormValidateMode[] | undefined;
|
|
75
109
|
}
|
|
76
110
|
|
|
77
111
|
/** Configures form creation without schema-level submit value transformation. */
|
|
78
|
-
export interface CreateFormOptionsWithoutSchema<
|
|
79
|
-
extends
|
|
112
|
+
export interface CreateFormOptionsWithoutSchema<
|
|
113
|
+
TValues extends FormValues,
|
|
114
|
+
> extends BaseCreateFormOptions<TValues> {
|
|
80
115
|
schema?: undefined;
|
|
81
116
|
}
|
|
82
117
|
|
|
83
118
|
/** Configures form creation with a Standard Schema validator that may transform submit values. */
|
|
84
|
-
export interface CreateFormOptionsWithSchema<
|
|
85
|
-
extends
|
|
119
|
+
export interface CreateFormOptionsWithSchema<
|
|
120
|
+
TValues extends FormValues,
|
|
121
|
+
TSubmitValues,
|
|
122
|
+
> extends BaseCreateFormOptions<TValues> {
|
|
86
123
|
schema: StandardSchemaV1<TValues, TSubmitValues>;
|
|
87
124
|
}
|
|
88
125
|
|
|
@@ -129,6 +166,7 @@ export interface ServerActionErrors<TValues extends FormValues> {
|
|
|
129
166
|
/** Provides reactive form state, field access, validation, submit, reset, and error controls. */
|
|
130
167
|
export interface FormApi<TValues extends FormValues, TSubmitValues> {
|
|
131
168
|
readonly state: ReadonlyCell<FormState<TValues>>;
|
|
169
|
+
fieldArray<Name extends FieldName<TValues>>(name: Name): FieldArrayApi<TValues, Name>;
|
|
132
170
|
field<Name extends FieldName<TValues>>(name: Name): FieldApi<TValues, Name>;
|
|
133
171
|
getValues(): TValues;
|
|
134
172
|
reset(values?: TValues): void;
|
|
@@ -168,6 +206,17 @@ export function createForm<TValues extends FormValues, TSubmitValues = TValues>(
|
|
|
168
206
|
});
|
|
169
207
|
const validationGenerations = new Map<FieldName<TValues>, number>();
|
|
170
208
|
const dirtyFields = new Set<FieldName<TValues>>();
|
|
209
|
+
const dependentFields = buildDependentFields(options.validate);
|
|
210
|
+
const fieldArrayKeys = new Map<FieldName<TValues>, string[]>();
|
|
211
|
+
const fieldStateCells = new Map<
|
|
212
|
+
FieldName<TValues>,
|
|
213
|
+
ReadonlyCell<FieldState<TValues[FieldName<TValues>]>>
|
|
214
|
+
>();
|
|
215
|
+
const fieldArrayCells = new Map<
|
|
216
|
+
FieldName<TValues>,
|
|
217
|
+
ReadonlyCell<Array<FieldArrayRow<ArrayFieldValue<TValues, FieldName<TValues>>>>>
|
|
218
|
+
>();
|
|
219
|
+
let nextFieldArrayKey = 0;
|
|
171
220
|
let activeSubmit: Promise<FormSubmitResult<TValues, unknown>> | undefined;
|
|
172
221
|
|
|
173
222
|
function commit(patch: Partial<FormState<TValues>>, dirty = dirtyFields.size > 0): void {
|
|
@@ -185,8 +234,21 @@ export function createForm<TValues extends FormValues, TSubmitValues = TValues>(
|
|
|
185
234
|
commit({ errors: normalizeErrors(errors) });
|
|
186
235
|
}
|
|
187
236
|
|
|
237
|
+
function invalidateFieldValidations(names: readonly FieldName<TValues>[]): void {
|
|
238
|
+
if (names.length === 0) {
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const validating = { ...state.get().validating };
|
|
243
|
+
for (const name of names) {
|
|
244
|
+
validationGenerations.set(name, (validationGenerations.get(name) ?? 0) + 1);
|
|
245
|
+
validating[name] = false;
|
|
246
|
+
}
|
|
247
|
+
commit({ validating });
|
|
248
|
+
}
|
|
249
|
+
|
|
188
250
|
async function validateField<Name extends FieldName<TValues>>(name: Name): Promise<void> {
|
|
189
|
-
const validator = options.validate?.[name];
|
|
251
|
+
const validator = validatorForField(options.validate?.[name]);
|
|
190
252
|
const generation = (validationGenerations.get(name) ?? 0) + 1;
|
|
191
253
|
validationGenerations.set(name, generation);
|
|
192
254
|
|
|
@@ -246,7 +308,7 @@ export function createForm<TValues extends FormValues, TSubmitValues = TValues>(
|
|
|
246
308
|
});
|
|
247
309
|
|
|
248
310
|
if (validateOn.has("change")) {
|
|
249
|
-
await
|
|
311
|
+
await validateFields([name, ...dependentFieldsFor(name)]);
|
|
250
312
|
}
|
|
251
313
|
}
|
|
252
314
|
|
|
@@ -259,20 +321,23 @@ export function createForm<TValues extends FormValues, TSubmitValues = TValues>(
|
|
|
259
321
|
});
|
|
260
322
|
|
|
261
323
|
if (validateOn.has("blur")) {
|
|
262
|
-
await
|
|
324
|
+
await validateFields([name, ...dependentFieldsFor(name)]);
|
|
263
325
|
}
|
|
264
326
|
}
|
|
265
327
|
|
|
266
328
|
async function validateForm(): Promise<FormValidationResult<TValues, TSubmitValues>> {
|
|
267
329
|
const values = state.get().values;
|
|
268
330
|
const errors: FormErrors<TValues> = {};
|
|
331
|
+
const fieldNames = Object.keys(options.validate ?? {}) as Array<FieldName<TValues>>;
|
|
332
|
+
invalidateFieldValidations(fieldNames);
|
|
269
333
|
|
|
270
|
-
for (const name of
|
|
334
|
+
for (const name of fieldNames) {
|
|
271
335
|
const validator = options.validate?.[name];
|
|
272
|
-
|
|
336
|
+
const validate = validatorForField(validator);
|
|
337
|
+
if (validate === undefined) {
|
|
273
338
|
continue;
|
|
274
339
|
}
|
|
275
|
-
const fieldErrors = normalizeFieldErrors(await
|
|
340
|
+
const fieldErrors = normalizeFieldErrors(await validate(values[name], values));
|
|
276
341
|
if (fieldErrors.length > 0) {
|
|
277
342
|
errors[name] = fieldErrors;
|
|
278
343
|
}
|
|
@@ -307,13 +372,164 @@ export function createForm<TValues extends FormValues, TSubmitValues = TValues>(
|
|
|
307
372
|
};
|
|
308
373
|
}
|
|
309
374
|
|
|
375
|
+
async function validateFields(names: readonly FieldName<TValues>[]): Promise<void> {
|
|
376
|
+
const uniqueNames = [...new Set(names)];
|
|
377
|
+
await Promise.all(uniqueNames.map((fieldName) => validateField(fieldName)));
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
function dependentFieldsFor(name: FieldName<TValues>): readonly FieldName<TValues>[] {
|
|
381
|
+
return dependentFields.get(name) ?? [];
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function arrayValues<Name extends FieldName<TValues>>(
|
|
385
|
+
name: Name,
|
|
386
|
+
): Array<ArrayFieldValue<TValues, Name>> {
|
|
387
|
+
const value = state.get().values[name];
|
|
388
|
+
return Array.isArray(value) ? ([...value] as Array<ArrayFieldValue<TValues, Name>>) : [];
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function createFieldArrayKey(name: FieldName<TValues>): string {
|
|
392
|
+
const key = `${name}:${nextFieldArrayKey}`;
|
|
393
|
+
nextFieldArrayKey += 1;
|
|
394
|
+
return key;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function ensureFieldArrayKeys(name: FieldName<TValues>, length: number): string[] {
|
|
398
|
+
const keys = fieldArrayKeys.get(name) ?? [];
|
|
399
|
+
if (keys.length > length) {
|
|
400
|
+
keys.length = length;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
while (keys.length < length) {
|
|
404
|
+
keys.push(createFieldArrayKey(name));
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
fieldArrayKeys.set(name, keys);
|
|
408
|
+
return keys;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
function fieldArrayRows<Name extends FieldName<TValues>>(
|
|
412
|
+
name: Name,
|
|
413
|
+
): Array<FieldArrayRow<ArrayFieldValue<TValues, Name>>> {
|
|
414
|
+
const values = arrayValues(name);
|
|
415
|
+
const keys = ensureFieldArrayKeys(name, values.length);
|
|
416
|
+
return values.map((value, index) => ({
|
|
417
|
+
index,
|
|
418
|
+
key: keys[index] ?? createFieldArrayKey(name),
|
|
419
|
+
value,
|
|
420
|
+
}));
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
function fieldArrayCell<Name extends FieldName<TValues>>(
|
|
424
|
+
name: Name,
|
|
425
|
+
): ReadonlyCell<Array<FieldArrayRow<ArrayFieldValue<TValues, Name>>>> {
|
|
426
|
+
const existing = fieldArrayCells.get(name);
|
|
427
|
+
if (existing !== undefined) {
|
|
428
|
+
return existing as ReadonlyCell<Array<FieldArrayRow<ArrayFieldValue<TValues, Name>>>>;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
const next = computed(() => fieldArrayRows(name), { equals: fieldArrayRowsEqual });
|
|
432
|
+
fieldArrayCells.set(
|
|
433
|
+
name,
|
|
434
|
+
next as ReadonlyCell<Array<FieldArrayRow<ArrayFieldValue<TValues, FieldName<TValues>>>>>,
|
|
435
|
+
);
|
|
436
|
+
return next;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
function fieldStateCell<Name extends FieldName<TValues>>(
|
|
440
|
+
name: Name,
|
|
441
|
+
): ReadonlyCell<FieldState<TValues[Name]>> {
|
|
442
|
+
const existing = fieldStateCells.get(name);
|
|
443
|
+
if (existing !== undefined) {
|
|
444
|
+
return existing as ReadonlyCell<FieldState<TValues[Name]>>;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
const next = computed(() => fieldState(state.get(), name), { equals: fieldStateEquals });
|
|
448
|
+
fieldStateCells.set(name, next as ReadonlyCell<FieldState<TValues[FieldName<TValues>]>>);
|
|
449
|
+
return next;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
async function setArrayValue<Name extends FieldName<TValues>>(
|
|
453
|
+
name: Name,
|
|
454
|
+
values: Array<ArrayFieldValue<TValues, Name>>,
|
|
455
|
+
keys: string[],
|
|
456
|
+
): Promise<void> {
|
|
457
|
+
fieldArrayKeys.set(name, keys);
|
|
458
|
+
await setValue(name, values as TValues[Name]);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
async function validateArrayField<Name extends FieldName<TValues>>(name: Name): Promise<void> {
|
|
462
|
+
if (validateOn.has("change")) {
|
|
463
|
+
await validateFields([name, ...dependentFieldsFor(name)]);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
310
467
|
return {
|
|
311
468
|
state,
|
|
312
|
-
|
|
469
|
+
fieldArray<Name extends FieldName<TValues>>(name: Name): FieldArrayApi<TValues, Name> {
|
|
313
470
|
return {
|
|
314
|
-
|
|
315
|
-
|
|
471
|
+
fields: fieldArrayCell(name),
|
|
472
|
+
async append(value) {
|
|
473
|
+
const values = arrayValues(name);
|
|
474
|
+
const keys = [...ensureFieldArrayKeys(name, values.length), createFieldArrayKey(name)];
|
|
475
|
+
values.push(value);
|
|
476
|
+
await setArrayValue(name, values, keys);
|
|
316
477
|
},
|
|
478
|
+
async insert(index, value) {
|
|
479
|
+
const values = arrayValues(name);
|
|
480
|
+
const insertIndex = clampIndex(index, 0, values.length);
|
|
481
|
+
const keys = [...ensureFieldArrayKeys(name, values.length)];
|
|
482
|
+
values.splice(insertIndex, 0, value);
|
|
483
|
+
keys.splice(insertIndex, 0, createFieldArrayKey(name));
|
|
484
|
+
await setArrayValue(name, values, keys);
|
|
485
|
+
},
|
|
486
|
+
async move(from, to) {
|
|
487
|
+
const values = arrayValues(name);
|
|
488
|
+
const keys = [...ensureFieldArrayKeys(name, values.length)];
|
|
489
|
+
if (!isArrayIndex(from, values.length)) {
|
|
490
|
+
await validateArrayField(name);
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
const [value] = values.splice(from, 1);
|
|
495
|
+
const [key] = keys.splice(from, 1);
|
|
496
|
+
const insertIndex = clampIndex(to, 0, values.length);
|
|
497
|
+
values.splice(insertIndex, 0, value as ArrayFieldValue<TValues, Name>);
|
|
498
|
+
keys.splice(insertIndex, 0, key as string);
|
|
499
|
+
await setArrayValue(name, values, keys);
|
|
500
|
+
},
|
|
501
|
+
async remove(index) {
|
|
502
|
+
const values = arrayValues(name);
|
|
503
|
+
const keys = [...ensureFieldArrayKeys(name, values.length)];
|
|
504
|
+
if (!isArrayIndex(index, values.length)) {
|
|
505
|
+
await validateArrayField(name);
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
values.splice(index, 1);
|
|
510
|
+
keys.splice(index, 1);
|
|
511
|
+
await setArrayValue(name, values, keys);
|
|
512
|
+
},
|
|
513
|
+
async swap(first, second) {
|
|
514
|
+
const values = arrayValues(name);
|
|
515
|
+
const keys = [...ensureFieldArrayKeys(name, values.length)];
|
|
516
|
+
if (!isArrayIndex(first, values.length) || !isArrayIndex(second, values.length)) {
|
|
517
|
+
await validateArrayField(name);
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
[values[first], values[second]] = [
|
|
522
|
+
values[second] as ArrayFieldValue<TValues, Name>,
|
|
523
|
+
values[first] as ArrayFieldValue<TValues, Name>,
|
|
524
|
+
];
|
|
525
|
+
[keys[first], keys[second]] = [keys[second] as string, keys[first] as string];
|
|
526
|
+
await setArrayValue(name, values, keys);
|
|
527
|
+
},
|
|
528
|
+
};
|
|
529
|
+
},
|
|
530
|
+
field<Name extends FieldName<TValues>>(name: Name): FieldApi<TValues, Name> {
|
|
531
|
+
return {
|
|
532
|
+
state: fieldStateCell(name),
|
|
317
533
|
bind(bindingOptions = {}) {
|
|
318
534
|
const bindingEvent = bindingOptions.event ?? "input";
|
|
319
535
|
const updateValue = async (inputEvent: Event) => {
|
|
@@ -355,6 +571,7 @@ export function createForm<TValues extends FormValues, TSubmitValues = TValues>(
|
|
|
355
571
|
|
|
356
572
|
initialValues = cloneValues(values);
|
|
357
573
|
dirtyFields.clear();
|
|
574
|
+
fieldArrayKeys.clear();
|
|
358
575
|
commit({
|
|
359
576
|
errors: {},
|
|
360
577
|
initialValues,
|
|
@@ -459,6 +676,55 @@ function fieldState<TValues extends FormValues, Name extends FieldName<TValues>>
|
|
|
459
676
|
};
|
|
460
677
|
}
|
|
461
678
|
|
|
679
|
+
function fieldStateEquals<TValue>(previous: FieldState<TValue>, next: FieldState<TValue>): boolean {
|
|
680
|
+
return (
|
|
681
|
+
previous.dirty === next.dirty &&
|
|
682
|
+
previous.touched === next.touched &&
|
|
683
|
+
previous.validating === next.validating &&
|
|
684
|
+
Object.is(previous.value, next.value) &&
|
|
685
|
+
stringArraysEqual(previous.errors, next.errors)
|
|
686
|
+
);
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
function fieldArrayRowsEqual<TValue>(
|
|
690
|
+
previous: Array<FieldArrayRow<TValue>>,
|
|
691
|
+
next: Array<FieldArrayRow<TValue>>,
|
|
692
|
+
): boolean {
|
|
693
|
+
if (previous.length !== next.length) {
|
|
694
|
+
return false;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
for (let index = 0; index < previous.length; index += 1) {
|
|
698
|
+
const previousRow = previous[index];
|
|
699
|
+
const nextRow = next[index];
|
|
700
|
+
if (
|
|
701
|
+
previousRow === undefined ||
|
|
702
|
+
nextRow === undefined ||
|
|
703
|
+
previousRow.index !== nextRow.index ||
|
|
704
|
+
previousRow.key !== nextRow.key ||
|
|
705
|
+
!Object.is(previousRow.value, nextRow.value)
|
|
706
|
+
) {
|
|
707
|
+
return false;
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
return true;
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
function stringArraysEqual(previous: readonly string[], next: readonly string[]): boolean {
|
|
715
|
+
if (previous.length !== next.length) {
|
|
716
|
+
return false;
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
for (let index = 0; index < previous.length; index += 1) {
|
|
720
|
+
if (previous[index] !== next[index]) {
|
|
721
|
+
return false;
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
return true;
|
|
726
|
+
}
|
|
727
|
+
|
|
462
728
|
function eventValue(event: Event, currentValue: unknown): unknown {
|
|
463
729
|
const target = event.currentTarget ?? event.target;
|
|
464
730
|
|
|
@@ -532,6 +798,45 @@ function normalizeFieldErrors(errors: readonly string[] | string | undefined): s
|
|
|
532
798
|
return typeof errors === "string" ? [errors] : [...errors];
|
|
533
799
|
}
|
|
534
800
|
|
|
801
|
+
function clampIndex(index: number, min: number, max: number): number {
|
|
802
|
+
return Math.min(Math.max(Math.trunc(index), min), max);
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
function isArrayIndex(index: number, length: number): boolean {
|
|
806
|
+
return Number.isInteger(index) && index >= 0 && index < length;
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
function validatorForField<TValue, TValues extends FormValues>(
|
|
810
|
+
entry: FieldValidationEntry<TValue, TValues> | undefined,
|
|
811
|
+
): FieldValidator<TValue, TValues> | undefined {
|
|
812
|
+
return typeof entry === "function" ? entry : entry?.validate;
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
function buildDependentFields<TValues extends FormValues>(
|
|
816
|
+
validate: BaseCreateFormOptions<TValues>["validate"],
|
|
817
|
+
): Map<FieldName<TValues>, FieldName<TValues>[]> {
|
|
818
|
+
const dependents = new Map<FieldName<TValues>, FieldName<TValues>[]>();
|
|
819
|
+
|
|
820
|
+
for (const [fieldName, entry] of Object.entries(validate ?? {}) as Array<
|
|
821
|
+
[FieldName<TValues>, FieldValidationEntry<TValues[FieldName<TValues>], TValues> | undefined]
|
|
822
|
+
>) {
|
|
823
|
+
if (entry === undefined || typeof entry === "function") {
|
|
824
|
+
continue;
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
for (const dep of entry.deps ?? []) {
|
|
828
|
+
const fields = dependents.get(dep);
|
|
829
|
+
if (fields === undefined) {
|
|
830
|
+
dependents.set(dep, [fieldName]);
|
|
831
|
+
continue;
|
|
832
|
+
}
|
|
833
|
+
fields.push(fieldName);
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
return dependents;
|
|
838
|
+
}
|
|
839
|
+
|
|
535
840
|
function cloneValues<TValues extends FormValues>(values: TValues): TValues {
|
|
536
841
|
return { ...values };
|
|
537
842
|
}
|