mayoiga 0.1.4 → 0.1.6
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/component.d.ts +4 -3
- package/dist/index.d.ts +27 -14
- package/dist/index.js +2 -1
- package/dist/index.test.js +15 -1
- package/package.json +20 -20
- package/src/component.tsx +1 -1
- package/src/index.test.tsx +23 -1
- package/src/index.ts +23 -4
- package/yarn.lock +1181 -734
package/dist/component.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import React from 'react';
|
|
1
2
|
import type { SliceProps, FieldProps, StateRestriction, Controller } from './index';
|
|
2
|
-
|
|
3
|
+
type ControlledComponentProps<State extends StateRestriction, Props> = Props & {
|
|
3
4
|
controller: Controller<State>;
|
|
4
5
|
};
|
|
5
|
-
export declare function Slicer<State extends StateRestriction, R extends ReadonlyArray<unknown>>(props: ControlledComponentProps<State, SliceProps<State, R>>): JSX.Element;
|
|
6
|
-
export declare function Field<State extends StateRestriction, Name extends keyof State>(props: ControlledComponentProps<State, FieldProps<State, Name>>): JSX.Element;
|
|
6
|
+
export declare function Slicer<State extends StateRestriction, R extends ReadonlyArray<unknown>>(props: ControlledComponentProps<State, SliceProps<State, R>>): React.JSX.Element;
|
|
7
|
+
export declare function Field<State extends StateRestriction, Name extends keyof State>(props: ControlledComponentProps<State, FieldProps<State, Name>>): React.JSX.Element;
|
|
7
8
|
export {};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
import type { Subscriber } from 'nozuchi';
|
|
2
2
|
import type { BaseSyntheticEvent, ChangeEvent, ReactNode, ReactElement } from 'react';
|
|
3
3
|
import type { infer as zodInfer, ZodIssue, ZodType } from 'zod';
|
|
4
|
-
export
|
|
5
|
-
|
|
4
|
+
export type StateRestriction = Record<string, any>;
|
|
5
|
+
type FormStatus = {
|
|
6
6
|
isDirty: boolean;
|
|
7
7
|
isValid: boolean;
|
|
8
8
|
};
|
|
9
|
-
|
|
9
|
+
type FormErrors<State extends StateRestriction> = {
|
|
10
10
|
[k in keyof State]: string | null;
|
|
11
11
|
};
|
|
12
|
-
export
|
|
12
|
+
export type FullFormState<State extends StateRestriction> = FormStatus & {
|
|
13
13
|
value: State;
|
|
14
14
|
errors: FormErrors<State>;
|
|
15
15
|
};
|
|
16
|
-
export
|
|
17
|
-
export
|
|
16
|
+
export type HandleChangeAction<State extends StateRestriction, R = void> = <Name extends keyof State>(name: Name, value: State[Name]) => R;
|
|
17
|
+
export type FieldProps<State extends StateRestriction, Name extends keyof State> = {
|
|
18
18
|
name: Name;
|
|
19
19
|
children: (tool: {
|
|
20
20
|
name: Name;
|
|
@@ -28,7 +28,7 @@ export declare type FieldProps<State extends StateRestriction, Name extends keyo
|
|
|
28
28
|
*/
|
|
29
29
|
deps?: ReadonlyArray<unknown>;
|
|
30
30
|
};
|
|
31
|
-
export
|
|
31
|
+
export type SliceProps<State extends StateRestriction, Selected extends ReadonlyArray<unknown>> = {
|
|
32
32
|
selector: (s: FullFormState<State>) => Selected;
|
|
33
33
|
children: (tool: {
|
|
34
34
|
handleChange: HandleChangeAction<State>;
|
|
@@ -43,14 +43,19 @@ export declare type SliceProps<State extends StateRestriction, Selected extends
|
|
|
43
43
|
*/
|
|
44
44
|
deps?: ReadonlyArray<unknown>;
|
|
45
45
|
};
|
|
46
|
-
|
|
46
|
+
type FormControllerBehavior<StateBeforeValidation extends StateRestriction> = {
|
|
47
47
|
/** For testing */
|
|
48
48
|
peek: (eyeball: (prev: FullFormState<StateBeforeValidation>) => void) => (prev: FullFormState<StateBeforeValidation>) => FullFormState<StateBeforeValidation>;
|
|
49
49
|
reset: () => () => FullFormState<StateBeforeValidation>;
|
|
50
50
|
/**
|
|
51
51
|
* Initialize form state without validation.
|
|
52
|
+
*
|
|
53
|
+
* @param initialVal value to initialize form state
|
|
54
|
+
* @param opts.cleanup if true, form state is become pristine (which means, `form.isDirty` is going to be false) .
|
|
52
55
|
*/
|
|
53
|
-
initializeForm: (initialVal: Partial<StateBeforeValidation
|
|
56
|
+
initializeForm: (initialVal: Partial<StateBeforeValidation>, opts?: {
|
|
57
|
+
cleanup?: true;
|
|
58
|
+
}) => (prev: FullFormState<StateBeforeValidation>) => FullFormState<StateBeforeValidation>;
|
|
54
59
|
/**
|
|
55
60
|
* Handle issue that zod reported
|
|
56
61
|
*/
|
|
@@ -66,9 +71,17 @@ declare type FormControllerBehavior<StateBeforeValidation> = {
|
|
|
66
71
|
reduceFormState: (reducer: (prevValue: StateBeforeValidation) => StateBeforeValidation) => (prev: FullFormState<StateBeforeValidation>) => FullFormState<StateBeforeValidation>;
|
|
67
72
|
handleBulkChange: (setter: (prev: StateBeforeValidation) => Partial<StateBeforeValidation>) => (prev: FullFormState<StateBeforeValidation>) => FullFormState<StateBeforeValidation>;
|
|
68
73
|
};
|
|
69
|
-
|
|
74
|
+
type ActionsCanBePublic<State extends StateRestriction> = {
|
|
70
75
|
reset: VoidFunction;
|
|
71
|
-
|
|
76
|
+
/**
|
|
77
|
+
* Initialize form state without validation.
|
|
78
|
+
*
|
|
79
|
+
* @param initialVal value to initialize form state
|
|
80
|
+
* @param opts.cleanup if true, form state is become pristine (which means, `form.isDirty` is going to be false) .
|
|
81
|
+
*/
|
|
82
|
+
initializeForm: (initial: Partial<State>, opts?: {
|
|
83
|
+
cleanup?: true;
|
|
84
|
+
}) => void;
|
|
72
85
|
pushFormErrors: (validator: (state: State) => Partial<FormErrors<State>>) => void;
|
|
73
86
|
handleChange: HandleChangeAction<State>;
|
|
74
87
|
handleBulkChange: (setter: (prev: State) => Partial<State>) => void;
|
|
@@ -77,7 +90,7 @@ declare type ActionsCanBePublic<State extends StateRestriction> = {
|
|
|
77
90
|
* Restricted type interface of the form store
|
|
78
91
|
* TODO: more restriction
|
|
79
92
|
*/
|
|
80
|
-
export
|
|
93
|
+
export type Controller<State extends StateRestriction> = {
|
|
81
94
|
useInitialize: (initialValue: Partial<State>) => void;
|
|
82
95
|
useSelector: <R>(selector: (state: FullFormState<State>) => R, isEqual?: (prev: R, current: R) => boolean) => R;
|
|
83
96
|
actions: ActionsCanBePublic<State>;
|
|
@@ -86,7 +99,7 @@ export declare type Controller<State extends StateRestriction> = {
|
|
|
86
99
|
Slicer: <Selected extends ReadonlyArray<unknown>>(props: SliceProps<State, Selected>) => ReactElement<any, any> | null;
|
|
87
100
|
};
|
|
88
101
|
};
|
|
89
|
-
|
|
102
|
+
type FormHook<State extends StateRestriction, Schema extends ZodType<any, any, any>> = {
|
|
90
103
|
controller: Controller<State>;
|
|
91
104
|
handleSubmit: (handler: (e: BaseSyntheticEvent) => (val: {
|
|
92
105
|
success: true;
|
|
@@ -101,6 +114,6 @@ declare type FormHook<State extends StateRestriction, Schema extends ZodType<any
|
|
|
101
114
|
/**
|
|
102
115
|
* @typeParam InitialState should be shallow
|
|
103
116
|
*/
|
|
104
|
-
export declare function createFormHook<StateBeforeValidation, Schema extends ZodType<any, any, any>>(initialState: StateBeforeValidation, schema: Schema): FormHook<StateBeforeValidation, Schema>;
|
|
117
|
+
export declare function createFormHook<StateBeforeValidation extends StateRestriction, Schema extends ZodType<any, any, any>>(initialState: StateBeforeValidation, schema: Schema): FormHook<StateBeforeValidation, Schema>;
|
|
105
118
|
export declare function useFormSlice<State extends StateRestriction, Sliced>(formController: Controller<State>, selector: (s: FullFormState<State>) => Sliced): readonly [Sliced, ActionsCanBePublic<State>];
|
|
106
119
|
export {};
|
package/dist/index.js
CHANGED
|
@@ -34,10 +34,11 @@ function createFormStore(initialState, schema) {
|
|
|
34
34
|
return prev;
|
|
35
35
|
},
|
|
36
36
|
reset: () => () => fullInitialState,
|
|
37
|
-
initializeForm: (initialVal) => (prev) => ({
|
|
37
|
+
initializeForm: (initialVal, opts) => (prev) => ({
|
|
38
38
|
...prev,
|
|
39
39
|
value: { ...prev.value, ...initialVal },
|
|
40
40
|
errors: { ...initialErrors },
|
|
41
|
+
isDirty: opts?.cleanup === true ? false : prev.isDirty,
|
|
41
42
|
}),
|
|
42
43
|
handleIssues: (issues) => (prev) => {
|
|
43
44
|
// FIXME: handle duplication
|
package/dist/index.test.js
CHANGED
|
@@ -13,7 +13,8 @@ const testSchema = zod.object({
|
|
|
13
13
|
.refine((val) => (Number.isNaN(val) ? 0 : val)),
|
|
14
14
|
marked: zod.boolean(),
|
|
15
15
|
});
|
|
16
|
-
const
|
|
16
|
+
const initialState = { name: '', description: '', age: '', marked: false };
|
|
17
|
+
const createTestHook = () => createFormHook(initialState, testSchema);
|
|
17
18
|
const AgeInputComponent = (props) => {
|
|
18
19
|
const { controller } = props;
|
|
19
20
|
const [[age, errorMessage], { handleChange }] = useFormSlice(controller, (s) => [s.value.age, s.errors.age]);
|
|
@@ -75,3 +76,16 @@ test('zod parse value before submit', async () => {
|
|
|
75
76
|
});
|
|
76
77
|
});
|
|
77
78
|
});
|
|
79
|
+
test('User is able to re-initialize the form state', async () => {
|
|
80
|
+
const TestFormHook = createTestHook();
|
|
81
|
+
const Top = () => {
|
|
82
|
+
return (React.createElement("div", null,
|
|
83
|
+
React.createElement(AgeInputComponent, { controller: TestFormHook.controller })));
|
|
84
|
+
};
|
|
85
|
+
render(React.createElement(Top, null));
|
|
86
|
+
const ageInput = screen.getByTestId('age-input');
|
|
87
|
+
await userEvent.type(ageInput, '9999');
|
|
88
|
+
expect(TestFormHook.api.getState().isDirty).toBeTruthy();
|
|
89
|
+
TestFormHook.actions.initializeForm({}, { cleanup: true });
|
|
90
|
+
expect(TestFormHook.api.getState().isDirty).toBeFalsy();
|
|
91
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mayoiga",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "Type safe hook based form library for React",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -40,30 +40,30 @@
|
|
|
40
40
|
},
|
|
41
41
|
"homepage": "https://github.com/hachibeeDI/mayoiga#readme",
|
|
42
42
|
"peerDependencies": {
|
|
43
|
-
"zod": "^3.
|
|
43
|
+
"zod": "^3.20.2"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"nozuchi": "^0.0.
|
|
46
|
+
"nozuchi": "^0.0.4"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
|
-
"@testing-library/react": "^
|
|
50
|
-
"@testing-library/user-event": "^14.
|
|
49
|
+
"@testing-library/react": "^14.0.0",
|
|
50
|
+
"@testing-library/user-event": "^14.4.3",
|
|
51
51
|
"@types/node": "^17.0.23",
|
|
52
|
-
"@types/react": "^18.
|
|
52
|
+
"@types/react": "^18.2.7",
|
|
53
53
|
"@types/use-sync-external-store": "^0.0.3",
|
|
54
|
-
"@typescript-eslint/eslint-plugin": "^5.
|
|
55
|
-
"@typescript-eslint/parser": "^5.
|
|
56
|
-
"eslint": "^8.
|
|
57
|
-
"eslint-plugin-import": "^2.
|
|
58
|
-
"eslint-plugin-testing-library": "^5.
|
|
59
|
-
"jsdom": "^
|
|
60
|
-
"prettier": "^2.
|
|
61
|
-
"react": "^18.
|
|
62
|
-
"react-dom": "^18.
|
|
63
|
-
"typedoc": "^0.
|
|
64
|
-
"typescript": "^
|
|
65
|
-
"use-sync-external-store": "^1.
|
|
66
|
-
"vitest": "^0.
|
|
67
|
-
"zod": "^3.
|
|
54
|
+
"@typescript-eslint/eslint-plugin": "^5.59.8",
|
|
55
|
+
"@typescript-eslint/parser": "^5.59.8",
|
|
56
|
+
"eslint": "^8.41.0",
|
|
57
|
+
"eslint-plugin-import": "^2.27.5",
|
|
58
|
+
"eslint-plugin-testing-library": "^5.11.0",
|
|
59
|
+
"jsdom": "^22.1.0",
|
|
60
|
+
"prettier": "^2.8.8",
|
|
61
|
+
"react": "^18.2.0",
|
|
62
|
+
"react-dom": "^18.2.0",
|
|
63
|
+
"typedoc": "^0.24.7",
|
|
64
|
+
"typescript": "^5.0.4",
|
|
65
|
+
"use-sync-external-store": "^1.2.0",
|
|
66
|
+
"vitest": "^0.31.1",
|
|
67
|
+
"zod": "^3.20.2"
|
|
68
68
|
}
|
|
69
69
|
}
|
package/src/component.tsx
CHANGED
|
@@ -2,7 +2,7 @@ import React from 'react';
|
|
|
2
2
|
|
|
3
3
|
import type {SliceProps, FieldProps, StateRestriction, Controller} from './index';
|
|
4
4
|
|
|
5
|
-
type ControlledComponentProps<State, Props> = Props & {
|
|
5
|
+
type ControlledComponentProps<State extends StateRestriction, Props> = Props & {
|
|
6
6
|
controller: Controller<State>;
|
|
7
7
|
};
|
|
8
8
|
|
package/src/index.test.tsx
CHANGED
|
@@ -28,7 +28,8 @@ type FormStateBeforeValidation = {
|
|
|
28
28
|
marked: boolean;
|
|
29
29
|
};
|
|
30
30
|
|
|
31
|
-
const
|
|
31
|
+
const initialState = {name: '', description: '', age: '', marked: false};
|
|
32
|
+
const createTestHook = () => createFormHook(initialState as FormStateBeforeValidation, testSchema);
|
|
32
33
|
|
|
33
34
|
const AgeInputComponent = (props: {controller: Controller<FormStateBeforeValidation>}) => {
|
|
34
35
|
const {controller} = props;
|
|
@@ -120,3 +121,24 @@ test('zod parse value before submit', async () => {
|
|
|
120
121
|
});
|
|
121
122
|
});
|
|
122
123
|
});
|
|
124
|
+
|
|
125
|
+
test('User is able to re-initialize the form state', async () => {
|
|
126
|
+
const TestFormHook = createTestHook();
|
|
127
|
+
|
|
128
|
+
const Top = () => {
|
|
129
|
+
return (
|
|
130
|
+
<div>
|
|
131
|
+
<AgeInputComponent controller={TestFormHook.controller} />
|
|
132
|
+
</div>
|
|
133
|
+
);
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
render(<Top />);
|
|
137
|
+
|
|
138
|
+
const ageInput: HTMLInputElement = screen.getByTestId('age-input');
|
|
139
|
+
await userEvent.type(ageInput, '9999');
|
|
140
|
+
|
|
141
|
+
expect(TestFormHook.api.getState().isDirty).toBeTruthy();
|
|
142
|
+
TestFormHook.actions.initializeForm({}, {cleanup: true});
|
|
143
|
+
expect(TestFormHook.api.getState().isDirty).toBeFalsy();
|
|
144
|
+
});
|
package/src/index.ts
CHANGED
|
@@ -57,7 +57,7 @@ export type SliceProps<State extends StateRestriction, Selected extends Readonly
|
|
|
57
57
|
deps?: ReadonlyArray<unknown>;
|
|
58
58
|
};
|
|
59
59
|
|
|
60
|
-
type FormControllerBehavior<StateBeforeValidation> = {
|
|
60
|
+
type FormControllerBehavior<StateBeforeValidation extends StateRestriction> = {
|
|
61
61
|
/** For testing */
|
|
62
62
|
peek: (
|
|
63
63
|
eyeball: (prev: FullFormState<StateBeforeValidation>) => void,
|
|
@@ -67,9 +67,15 @@ type FormControllerBehavior<StateBeforeValidation> = {
|
|
|
67
67
|
|
|
68
68
|
/**
|
|
69
69
|
* Initialize form state without validation.
|
|
70
|
+
*
|
|
71
|
+
* @param initialVal value to initialize form state
|
|
72
|
+
* @param opts.cleanup if true, form state is become pristine (which means, `form.isDirty` is going to be false) .
|
|
70
73
|
*/
|
|
71
74
|
initializeForm: (
|
|
72
75
|
initialVal: Partial<StateBeforeValidation>,
|
|
76
|
+
opts?: {
|
|
77
|
+
cleanup?: true;
|
|
78
|
+
},
|
|
73
79
|
) => (prev: FullFormState<StateBeforeValidation>) => FullFormState<StateBeforeValidation>;
|
|
74
80
|
|
|
75
81
|
/**
|
|
@@ -151,10 +157,11 @@ function createFormStore<StateBeforeValidation extends StateRestriction, Schema
|
|
|
151
157
|
},
|
|
152
158
|
|
|
153
159
|
reset: () => () => fullInitialState,
|
|
154
|
-
initializeForm: (initialVal) => (prev) => ({
|
|
160
|
+
initializeForm: (initialVal, opts) => (prev) => ({
|
|
155
161
|
...prev,
|
|
156
162
|
value: {...prev.value, ...initialVal},
|
|
157
163
|
errors: {...initialErrors},
|
|
164
|
+
isDirty: opts?.cleanup === true ? false : prev.isDirty,
|
|
158
165
|
}),
|
|
159
166
|
handleIssues: (issues) => (prev) => {
|
|
160
167
|
// FIXME: handle duplication
|
|
@@ -196,7 +203,19 @@ function createFormStore<StateBeforeValidation extends StateRestriction, Schema
|
|
|
196
203
|
|
|
197
204
|
type ActionsCanBePublic<State extends StateRestriction> = {
|
|
198
205
|
reset: VoidFunction;
|
|
199
|
-
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Initialize form state without validation.
|
|
209
|
+
*
|
|
210
|
+
* @param initialVal value to initialize form state
|
|
211
|
+
* @param opts.cleanup if true, form state is become pristine (which means, `form.isDirty` is going to be false) .
|
|
212
|
+
*/
|
|
213
|
+
initializeForm: (
|
|
214
|
+
initial: Partial<State>,
|
|
215
|
+
opts?: {
|
|
216
|
+
cleanup?: true;
|
|
217
|
+
},
|
|
218
|
+
) => void;
|
|
200
219
|
pushFormErrors: (validator: (state: State) => Partial<FormErrors<State>>) => void;
|
|
201
220
|
handleChange: HandleChangeAction<State>;
|
|
202
221
|
handleBulkChange: (setter: (prev: State) => Partial<State>) => void;
|
|
@@ -234,7 +253,7 @@ type FormHook<State extends StateRestriction, Schema extends ZodType<any, any, a
|
|
|
234
253
|
/**
|
|
235
254
|
* @typeParam InitialState should be shallow
|
|
236
255
|
*/
|
|
237
|
-
export function createFormHook<StateBeforeValidation, Schema extends ZodType<any, any, any>>(
|
|
256
|
+
export function createFormHook<StateBeforeValidation extends StateRestriction, Schema extends ZodType<any, any, any>>(
|
|
238
257
|
initialState: StateBeforeValidation,
|
|
239
258
|
schema: Schema,
|
|
240
259
|
): FormHook<StateBeforeValidation, Schema> {
|