@homebound/form-state 2.16.0 → 2.17.1
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/dist/FormStateApp.d.ts +1 -1
- package/dist/config.d.ts +2 -2
- package/dist/fields/listField.d.ts +2 -2
- package/dist/fields/objectField.d.ts +6 -6
- package/dist/fields/valueField.d.ts +4 -4
- package/dist/formState.test.js +0 -18
- package/dist/rules.d.ts +1 -5
- package/dist/useFormState.test.js +10 -10
- package/dist/useFormStates.test.js +2 -1
- package/package.json +1 -1
package/dist/FormStateApp.d.ts
CHANGED
package/dist/config.d.ts
CHANGED
|
@@ -20,7 +20,7 @@ export declare type FragmentFieldConfig = {
|
|
|
20
20
|
/** Field configuration for primitive values, i.e. strings/numbers/Dates/user-defined types. */
|
|
21
21
|
export declare type ValueFieldConfig<T, V> = {
|
|
22
22
|
type: "value";
|
|
23
|
-
rules?: Rule<
|
|
23
|
+
rules?: Rule<V | null | undefined>[];
|
|
24
24
|
/**
|
|
25
25
|
* If true, marks this field as the id, which will be used for things like "always include in changedValue".
|
|
26
26
|
*
|
|
@@ -48,7 +48,7 @@ export declare type ValueFieldConfig<T, V> = {
|
|
|
48
48
|
export declare type ListFieldConfig<T, U> = {
|
|
49
49
|
type: "list";
|
|
50
50
|
/** Rules that can run on the full list of children. */
|
|
51
|
-
rules?: Rule<
|
|
51
|
+
rules?: Rule<readonly ObjectState<U>[]>[];
|
|
52
52
|
/** Config for each child's form state, i.e. each book. */
|
|
53
53
|
config: ObjectConfig<U>;
|
|
54
54
|
/**
|
|
@@ -3,9 +3,9 @@ import { ObjectState } from "./objectField";
|
|
|
3
3
|
import { FieldState } from "./valueField";
|
|
4
4
|
import { Rule } from "../rules";
|
|
5
5
|
/** Form state for list of children, i.e. `U` is a `Book` in a form with a `books: Book[]`. */
|
|
6
|
-
export interface ListFieldState<
|
|
6
|
+
export interface ListFieldState<U> extends Omit<FieldState<U[]>, "originalValue"> {
|
|
7
7
|
readonly rows: ReadonlyArray<ObjectState<U>>;
|
|
8
8
|
add(value: U, index?: number): void;
|
|
9
9
|
remove(indexOrValue: number | U): void;
|
|
10
10
|
}
|
|
11
|
-
export declare function newListFieldState<T, K extends keyof T, U>(parentInstance: T, parentState: () => ObjectState<T>, key: K, rules: Rule<
|
|
11
|
+
export declare function newListFieldState<T, K extends keyof T, U>(parentInstance: T, parentState: () => ObjectState<T>, key: K, rules: Rule<readonly ObjectState<U>[]>[], listConfig: ListFieldConfig<T, U>, config: ObjectConfig<U>, maybeAutoSave: () => void): ListFieldState<U>;
|
|
@@ -14,7 +14,7 @@ import { Builtin } from "../utils";
|
|
|
14
14
|
* have exactly the on-the-wire type `T` that they need to submit to the backend, without doing the
|
|
15
15
|
* manual mapping of "data that was in the form controls" into "data that the backend wants".
|
|
16
16
|
*
|
|
17
|
-
* Note that this can be hierarchical by having
|
|
17
|
+
* Note that this can be hierarchical by having a field of `ListFieldState` that
|
|
18
18
|
* themselves each wrap an `ObjectState`, i.e.:
|
|
19
19
|
*
|
|
20
20
|
* ```
|
|
@@ -28,14 +28,14 @@ import { Builtin } from "../utils";
|
|
|
28
28
|
* - title: FieldState
|
|
29
29
|
* ```
|
|
30
30
|
*/
|
|
31
|
-
export declare type ObjectState<T
|
|
31
|
+
export declare type ObjectState<T> = FieldStates<T> & FieldState<T> & {
|
|
32
32
|
/** Sets the state of fields in `state`. */
|
|
33
33
|
set(state: Partial<T>, opts?: SetOpts): void;
|
|
34
34
|
/** Returns whether the object can be saved, i.e. is valid, but also as a side-effect marks touched. */
|
|
35
35
|
canSave(): boolean;
|
|
36
36
|
};
|
|
37
|
-
export declare type ObjectStateInternal<T
|
|
38
|
-
set(value:
|
|
37
|
+
export declare type ObjectStateInternal<T> = ObjectState<T> & {
|
|
38
|
+
set(value: T, opts?: InternalSetOpts): void;
|
|
39
39
|
};
|
|
40
40
|
declare const fragmentSym: unique symbol;
|
|
41
41
|
export declare type Fragment<V> = V & {
|
|
@@ -44,7 +44,7 @@ export declare type Fragment<V> = V & {
|
|
|
44
44
|
export declare function fragment<V>(value: V): Fragment<V>;
|
|
45
45
|
/** For a given input type `T`, decorate each field into the "field state" type that holds our form-relevant state, i.e. valid/touched/etc. */
|
|
46
46
|
declare type FieldStates<T> = {
|
|
47
|
-
[K in keyof T]-?: T[K] extends Fragment<infer V> ? FragmentField<V> : T[K] extends Array<infer U> | null | undefined ? [U] extends [Builtin] ? FieldState<T
|
|
47
|
+
[K in keyof T]-?: T[K] extends Fragment<infer V> ? FragmentField<V> : T[K] extends Array<infer U> | null | undefined ? [U] extends [Builtin] ? FieldState<T[K]> : ListFieldState<U> : T[K] extends Builtin | null | undefined ? FieldState<T[K]> : ObjectState<T[K]>;
|
|
48
48
|
};
|
|
49
49
|
/**
|
|
50
50
|
* Creates a new `ObjectState` for a given form object `T` given config rules in `config`.
|
|
@@ -56,5 +56,5 @@ declare type FieldStates<T> = {
|
|
|
56
56
|
export declare function createObjectState<T>(config: ObjectConfig<T>, instance: T, opts?: {
|
|
57
57
|
maybeAutoSave?: () => void;
|
|
58
58
|
}): ObjectState<T>;
|
|
59
|
-
export declare function newObjectState<T, P = any>(config: ObjectConfig<T>, parentState: (() => ObjectState<P>) | undefined, parentListState: FieldState<any
|
|
59
|
+
export declare function newObjectState<T, P = any>(config: ObjectConfig<T>, parentState: (() => ObjectState<P>) | undefined, parentListState: FieldState<any> | undefined, instance: T, key: keyof T | undefined, maybeAutoSave: () => void): ObjectState<T>;
|
|
60
60
|
export {};
|
|
@@ -10,7 +10,7 @@ import { Rule } from "../rules";
|
|
|
10
10
|
* Note that `V` will always have `null | undefined` added to it by `FieldStates`, b/c most form fields
|
|
11
11
|
* i.e. text boxes, can always be cleared out/deleted.
|
|
12
12
|
*/
|
|
13
|
-
export interface FieldState<
|
|
13
|
+
export interface FieldState<V> {
|
|
14
14
|
readonly key: string;
|
|
15
15
|
value: V;
|
|
16
16
|
readonly originalValue: V;
|
|
@@ -21,7 +21,7 @@ export interface FieldState<T, V> {
|
|
|
21
21
|
readonly dirty: boolean;
|
|
22
22
|
readonly valid: boolean;
|
|
23
23
|
readonly isNewEntity: boolean;
|
|
24
|
-
rules: Rule<
|
|
24
|
+
rules: Rule<V>[];
|
|
25
25
|
readonly errors: string[];
|
|
26
26
|
/** Returns a subset of V with only the changed values. Currently not observable. */
|
|
27
27
|
readonly changedValue: V;
|
|
@@ -49,11 +49,11 @@ export interface InternalSetOpts extends SetOpts {
|
|
|
49
49
|
/** `refreshing` is when `useFormState` sees a new value. */
|
|
50
50
|
refreshing?: boolean;
|
|
51
51
|
}
|
|
52
|
-
export interface FieldStateInternal<T, V> extends FieldState<
|
|
52
|
+
export interface FieldStateInternal<T, V> extends FieldState<V> {
|
|
53
53
|
set(value: V, opts?: InternalSetOpts): void;
|
|
54
54
|
_isIdKey: boolean;
|
|
55
55
|
_isDeleteKey: boolean;
|
|
56
56
|
_isReadOnlyKey: boolean;
|
|
57
57
|
_focused: boolean;
|
|
58
58
|
}
|
|
59
|
-
export declare function newValueFieldState<T, K extends keyof T>(parentInstance: T, parentState: () => ObjectState<T>, key: K, rules: Rule<T
|
|
59
|
+
export declare function newValueFieldState<T, K extends keyof T>(parentInstance: T, parentState: () => ObjectState<T>, key: K, rules: Rule<T[K] | null | undefined>[], isIdKey: boolean, isDeleteKey: boolean, isReadOnlyKey: boolean, computed: boolean, readOnly: boolean, strictOrder: boolean, maybeAutoSave: () => void): FieldState<T[K] | null | undefined>;
|
package/dist/formState.test.js
CHANGED
|
@@ -941,24 +941,6 @@ describe("formState", () => {
|
|
|
941
941
|
formState.revertChanges();
|
|
942
942
|
expect(formState.firstName.value).toEqual("first");
|
|
943
943
|
});
|
|
944
|
-
it("lets rules validate against other fields", () => {
|
|
945
|
-
const formState = objectField_1.createObjectState({
|
|
946
|
-
firstName: { type: "value", rules: [] },
|
|
947
|
-
lastName: {
|
|
948
|
-
type: "value",
|
|
949
|
-
rules: [
|
|
950
|
-
({ object }) => {
|
|
951
|
-
if (object.firstName.value === object.lastName.value) {
|
|
952
|
-
return "Must not match first name";
|
|
953
|
-
}
|
|
954
|
-
},
|
|
955
|
-
],
|
|
956
|
-
},
|
|
957
|
-
}, {});
|
|
958
|
-
formState.firstName.value = "bob";
|
|
959
|
-
formState.lastName.value = "bob";
|
|
960
|
-
expect(formState.lastName.errors).toEqual(["Must not match first name"]);
|
|
961
|
-
});
|
|
962
944
|
it("can return only changed primitive fields", () => {
|
|
963
945
|
// Given an author
|
|
964
946
|
const formState = objectField_1.createObjectState(authorWithBooksConfig, {
|
package/dist/rules.d.ts
CHANGED
|
@@ -1,14 +1,10 @@
|
|
|
1
|
-
import { ObjectState } from "./fields/objectField";
|
|
2
|
-
declare type IfAny<T, Y, N> = 0 extends 1 & T ? Y : N;
|
|
3
1
|
/** A validation rule, given the value and name, return the error string if valid, or undefined if valid. */
|
|
4
|
-
export declare type Rule<
|
|
2
|
+
export declare type Rule<V> = (opts: {
|
|
5
3
|
value: V;
|
|
6
4
|
key: string;
|
|
7
5
|
originalValue: V;
|
|
8
|
-
object: IfAny<T, any, ObjectState<T>>;
|
|
9
6
|
}) => string | undefined;
|
|
10
7
|
/** A rule that validates `value` is not `undefined`, `null`, or empty string. */
|
|
11
8
|
export declare const required: <V>({ value: v }: {
|
|
12
9
|
value: V;
|
|
13
10
|
}) => string | undefined;
|
|
14
|
-
export {};
|
|
@@ -261,17 +261,17 @@ describe("useFormState", () => {
|
|
|
261
261
|
});
|
|
262
262
|
it("useFormState can accept new data with computed fields", async () => {
|
|
263
263
|
// Given a component
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
return this.firstName + " " + this.lastName;
|
|
273
|
-
}
|
|
264
|
+
// And it's using a class/mobx proxy as the basis for the data
|
|
265
|
+
class AuthorRow {
|
|
266
|
+
constructor(firstName, lastName) {
|
|
267
|
+
this.firstName = firstName;
|
|
268
|
+
this.lastName = lastName;
|
|
269
|
+
}
|
|
270
|
+
get fullName() {
|
|
271
|
+
return this.firstName + " " + this.lastName;
|
|
274
272
|
}
|
|
273
|
+
}
|
|
274
|
+
function TestComponent() {
|
|
275
275
|
// And we have two sets of data
|
|
276
276
|
const data1 = { firstName: "f1", lastName: "l1" };
|
|
277
277
|
const data2 = { firstName: "f2", lastName: "l2" };
|
|
@@ -39,7 +39,7 @@ describe("useFormStates", () => {
|
|
|
39
39
|
const [apiData, setApiData] = react_1.useState({ id: "a:1", firstName: "Brandon" });
|
|
40
40
|
const { getFormState } = useFormStates_1.useFormStates({ config, autoSave, getId: (o) => o.id });
|
|
41
41
|
// Memoize an original for comparing the update against.
|
|
42
|
-
const originalState = react_1.useMemo(() => getFormState(apiData), []);
|
|
42
|
+
const originalState = react_1.useMemo(() => getFormState(apiData), [getFormState]);
|
|
43
43
|
const state = getFormState(apiData);
|
|
44
44
|
return (jsx_runtime_1.jsxs("div", { children: [jsx_runtime_1.jsx("div", Object.assign({ "data-testid": "firstName" }, { children: state.firstName.value }), void 0),
|
|
45
45
|
jsx_runtime_1.jsx("div", Object.assign({ "data-testid": "statesEqual" }, { children: JSON.stringify(state === originalState) }), void 0),
|
|
@@ -124,6 +124,7 @@ describe("useFormStates", () => {
|
|
|
124
124
|
const [config, setConfig] = react_1.useState(originalConfig);
|
|
125
125
|
const { getFormState } = useFormStates_1.useFormStates({ config, autoSave, getId: (o) => o.id });
|
|
126
126
|
// Memoize an original for comparing the update against.
|
|
127
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
127
128
|
const originalState = react_1.useMemo(() => getFormState(apiData), []);
|
|
128
129
|
const state = getFormState(apiData);
|
|
129
130
|
return (jsx_runtime_1.jsxs("div", { children: [jsx_runtime_1.jsx("div", Object.assign({ "data-testid": "statesEqual" }, { children: JSON.stringify(state === originalState) }), void 0),
|