mayoiga 0.0.20 → 0.1.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/README.md +21 -45
- package/dist/component.d.ts +7 -0
- package/dist/component.js +11 -0
- package/dist/component.test.d.ts +1 -0
- package/dist/component.test.js +53 -0
- package/dist/index.d.ts +88 -0
- package/dist/index.js +146 -0
- package/dist/index.test.d.ts +1 -0
- package/dist/index.test.js +74 -0
- package/dist/utils.d.ts +2 -0
- package/dist/utils.js +6 -0
- package/package.json +39 -41
- package/lib/actions.d.ts +0 -28
- package/lib/actions.js +0 -29
- package/lib/actions.js.map +0 -1
- package/lib/forms.d.ts +0 -21
- package/lib/forms.js +0 -74
- package/lib/forms.js.map +0 -1
- package/lib/index.d.ts +0 -3
- package/lib/index.js +0 -7
- package/lib/index.js.map +0 -1
- package/lib/inputProtocol.d.ts +0 -17
- package/lib/inputProtocol.js +0 -24
- package/lib/inputProtocol.js.map +0 -1
- package/lib/mayoiga.d.ts +0 -107
- package/lib/mayoiga.js +0 -128
- package/lib/mayoiga.js.map +0 -1
- package/lib/reducer.d.ts +0 -20
- package/lib/reducer.js +0 -53
- package/lib/reducer.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
[](https://github.com/hachibeeDI/mayoiga/actions/workflows/checker.yml)
|
|
2
|
+
|
|
1
3
|
# Mayoiga
|
|
2
4
|
|
|
3
5
|
The React Hook based form library. Of course it is TypeScript friendly.
|
|
@@ -13,8 +15,6 @@ The React Hook based form library. Of course it is TypeScript friendly.
|
|
|
13
15
|
|
|
14
16
|
With less boilerplate code!!
|
|
15
17
|
|
|
16
|
-
You can see more working demo [under the example directory](example/src/demo-app.tsx) .
|
|
17
|
-
|
|
18
18
|
|
|
19
19
|
## How to use
|
|
20
20
|
|
|
@@ -23,62 +23,38 @@ You can see more working demo [under the example directory](example/src/demo-app
|
|
|
23
23
|
There are only two functions which you should know to use Mayoiga.
|
|
24
24
|
|
|
25
25
|
- [useForm](https://hachibeedi.github.io/mayoiga/modules/_mayoiga_.html#useform)
|
|
26
|
-
- [createFormScope](https://hachibeedi.github.io/mayoiga/modules/_mayoiga_.html#createformscope)
|
|
27
|
-
|
|
28
|
-
And if you would like to bring it to production, you need to know how to about [InputProtocol](https://hachibeedi.github.io/mayoiga/modules/_inputprotocol_.html#inputprotocol).
|
|
29
|
-
To know about that, `mayoiga/lib/forms` can be a good example.
|
|
30
26
|
|
|
31
|
-
|
|
27
|
+
- [createFormScope](https://hachibeedi.github.io/mayoiga/modules/_mayoiga_.html#createformscope)
|
|
32
28
|
|
|
33
29
|
|
|
34
30
|
### Minimum example:
|
|
35
31
|
|
|
36
32
|
```typeScript
|
|
37
33
|
|
|
38
|
-
|
|
39
|
-
import {FC} from 'react';
|
|
40
|
-
|
|
41
|
-
import {useForm, createFormScope} from 'mayoiga';
|
|
42
|
-
import {Input, NumberInput} from 'mayoiga/lib/forms';
|
|
34
|
+
const TestFormHook = createFormHook({name:'', description: '', age: ''} as FormStateBeforeValidation, testSchema);
|
|
43
35
|
|
|
44
|
-
const
|
|
36
|
+
const INPUT_AGE = '9';
|
|
37
|
+
const PARSED_RESULT = Number(INPUT_AGE);
|
|
45
38
|
|
|
46
|
-
const
|
|
47
|
-
if (
|
|
48
|
-
|
|
39
|
+
const handleSubmitTester = TestFormHook.handleSubmit((e) => (result) => {
|
|
40
|
+
if (result.success) {
|
|
41
|
+
expect(result.data.age).toBe(PARSED_RESULT);
|
|
42
|
+
} else {
|
|
43
|
+
expect('should not called').toBe(result.err);
|
|
49
44
|
}
|
|
50
|
-
|
|
51
|
-
return `less than ${max}`;
|
|
52
|
-
}
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
const choice = function<T>(...candidates: Array<T>) {
|
|
56
|
-
return (target: T) => (candidates.includes(target) ? undefined : 'You should choose from the candidates.');
|
|
57
|
-
};
|
|
45
|
+
});
|
|
58
46
|
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
47
|
+
const Top = () => {
|
|
48
|
+
return (
|
|
49
|
+
<div>
|
|
50
|
+
<AgeInputComponent formHook={TestFormHook} />
|
|
51
|
+
<button data-testid="submit-button" onClick={handleSubmitTester} />
|
|
52
|
+
</div>
|
|
53
|
+
)
|
|
63
54
|
};
|
|
64
55
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
const DemoForm = scope(props => {
|
|
68
|
-
const {Form, Field} = useForm(context);
|
|
69
|
-
return (
|
|
70
|
-
<Form onSubmit={value => console.log(value)}>
|
|
71
|
-
<Field name="name" component={Input} validations={[required]} />
|
|
72
|
-
<Field name="age" component={NumberInput} validations={[between(5, 20)]} />
|
|
73
|
-
<Field name="seed" component={Input} validations={[choice('fish', 'squid', 'octopus')]} />
|
|
74
|
-
|
|
75
|
-
<button disabled={!props.touched || Object.values(props.errors).some(e => !!e.length)}>submit</button>
|
|
76
|
-
</Form>
|
|
77
|
-
);
|
|
56
|
+
act(() => {
|
|
57
|
+
render(<Top />);
|
|
78
58
|
});
|
|
79
59
|
|
|
80
|
-
export default function DemoApp() {
|
|
81
|
-
return <DemoForm initialState={INITIAL_FORM_STATE} onSubmit={value => alert(`submit ${JSON.stringify(value)}`)} />;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
60
|
```
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { SliceProps, FieldProps, StateRestriction, Controller } from './index';
|
|
2
|
+
declare type ControlledComponentProps<State, Props> = Props & {
|
|
3
|
+
controller: Controller<State>;
|
|
4
|
+
};
|
|
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;
|
|
7
|
+
export {};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export function Slicer(props) {
|
|
3
|
+
const { controller, ...restProps } = props;
|
|
4
|
+
const { Slicer: Delegate } = controller.components;
|
|
5
|
+
return React.createElement(Delegate, { ...restProps });
|
|
6
|
+
}
|
|
7
|
+
export function Field(props) {
|
|
8
|
+
const { controller, ...restProps } = props;
|
|
9
|
+
const { Field: Delegate } = controller.components;
|
|
10
|
+
return React.createElement(Delegate, { ...restProps });
|
|
11
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { render, screen, waitFor } from '@testing-library/react';
|
|
2
|
+
import userEvent from '@testing-library/user-event';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import { test, expect } from 'vitest';
|
|
5
|
+
import * as zod from 'zod';
|
|
6
|
+
import { Field, Slicer } from './component';
|
|
7
|
+
import { createFormHook, } from './index';
|
|
8
|
+
const testSchema = zod.object({
|
|
9
|
+
name: zod.string(),
|
|
10
|
+
description: zod.string(),
|
|
11
|
+
age: zod.string().transform(val => Number(val)).refine(val => Number.isNaN(val) ? 0 : val),
|
|
12
|
+
marked: zod.boolean(),
|
|
13
|
+
});
|
|
14
|
+
const createTestHook = () => createFormHook({ name: '', description: '', age: '', marked: false }, testSchema);
|
|
15
|
+
// afterEach(cleanup);
|
|
16
|
+
test('Field component works fine in basic usage.', async () => {
|
|
17
|
+
const TestFormHook = createTestHook();
|
|
18
|
+
const Top = () => {
|
|
19
|
+
return (React.createElement("div", null,
|
|
20
|
+
React.createElement(Field, { controller: TestFormHook.controller, name: "age" }, (tool) => React.createElement("input", { "data-testid": "age-input", ...tool }))));
|
|
21
|
+
};
|
|
22
|
+
render(React.createElement(Top, null));
|
|
23
|
+
const INPUT_AGE = '42';
|
|
24
|
+
// calling `userEvent` in here will cause race condition...
|
|
25
|
+
await userEvent.type(screen.getByTestId('age-input'), INPUT_AGE);
|
|
26
|
+
await waitFor(() => {
|
|
27
|
+
const ageInput = screen.getByTestId('age-input');
|
|
28
|
+
expect(ageInput.value).toBe(INPUT_AGE);
|
|
29
|
+
TestFormHook.api.actions.peek((s) => {
|
|
30
|
+
expect(s.value.age).toBe(INPUT_AGE);
|
|
31
|
+
expect(s.errors.age).toBeNull();
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
test('Slice component works fine in basic usage.', async () => {
|
|
36
|
+
const TestFormHook = createTestHook();
|
|
37
|
+
const Top = () => {
|
|
38
|
+
return (React.createElement("div", null,
|
|
39
|
+
React.createElement(Slicer, { controller: TestFormHook.controller, selector: s => [s.value.age] }, (tool, age) => React.createElement("input", { "data-testid": "age-input", name: "age", value: age, onChange: e => tool.handleChange('age', e.target.value) }))));
|
|
40
|
+
};
|
|
41
|
+
render(React.createElement(Top, null));
|
|
42
|
+
const INPUT_AGE = '42';
|
|
43
|
+
// calling `userEvent` in here will cause race condition...
|
|
44
|
+
await userEvent.type(screen.getByTestId('age-input'), INPUT_AGE);
|
|
45
|
+
await waitFor(() => {
|
|
46
|
+
const ageInput = screen.getByTestId('age-input');
|
|
47
|
+
expect(ageInput.value).toBe(INPUT_AGE);
|
|
48
|
+
TestFormHook.api.actions.peek((s) => {
|
|
49
|
+
expect(s.value.age).toBe(INPUT_AGE);
|
|
50
|
+
expect(s.errors.age).toBeNull();
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
});
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import type { Subscriber } from 'nozuchi';
|
|
2
|
+
import type { BaseSyntheticEvent, ChangeEvent, ReactNode, ReactElement } from 'react';
|
|
3
|
+
import type { infer as zodInfer, ZodIssue, ZodType } from 'zod';
|
|
4
|
+
export declare type StateRestriction = Record<string, any>;
|
|
5
|
+
declare type FormStatus = {
|
|
6
|
+
isDirty: boolean;
|
|
7
|
+
isValid: boolean;
|
|
8
|
+
};
|
|
9
|
+
declare type FormErrors<State extends StateRestriction> = {
|
|
10
|
+
[k in keyof State]: string | null;
|
|
11
|
+
};
|
|
12
|
+
export declare type FullFormState<State extends StateRestriction> = FormStatus & {
|
|
13
|
+
value: State;
|
|
14
|
+
errors: FormErrors<State>;
|
|
15
|
+
};
|
|
16
|
+
export declare type HandleChangeAction<State extends StateRestriction, R = void> = <Name extends keyof State>(name: Name, value: State[Name]) => R;
|
|
17
|
+
export declare type FieldProps<State extends StateRestriction, Name extends keyof State> = {
|
|
18
|
+
name: Name;
|
|
19
|
+
children: (tool: {
|
|
20
|
+
name: Name;
|
|
21
|
+
value: State[Name];
|
|
22
|
+
onChange(this: void, name: Name, value: State[Name]): void;
|
|
23
|
+
onChange(this: void, e: ChangeEvent<HTMLElement>): void;
|
|
24
|
+
}, value: State[Name], errorMessage: string | null) => ReactNode;
|
|
25
|
+
};
|
|
26
|
+
export declare type SliceProps<State extends StateRestriction, Selected extends ReadonlyArray<unknown>> = {
|
|
27
|
+
selector: (s: FullFormState<State>) => Selected;
|
|
28
|
+
children: (tool: {
|
|
29
|
+
handleChange: HandleChangeAction<State>;
|
|
30
|
+
}, ...value: Readonly<Selected>) => ReactNode;
|
|
31
|
+
};
|
|
32
|
+
declare type FormControllerBehavior<StateBeforeValidation> = {
|
|
33
|
+
/** For testing */
|
|
34
|
+
peek: (eyeball: (prev: FullFormState<StateBeforeValidation>) => void) => (prev: FullFormState<StateBeforeValidation>) => FullFormState<StateBeforeValidation>;
|
|
35
|
+
reset: () => () => FullFormState<StateBeforeValidation>;
|
|
36
|
+
/**
|
|
37
|
+
* Initialize form state without validation.
|
|
38
|
+
*/
|
|
39
|
+
initializeForm: (initialVal: Partial<StateBeforeValidation>) => (prev: FullFormState<StateBeforeValidation>) => FullFormState<StateBeforeValidation>;
|
|
40
|
+
/**
|
|
41
|
+
* Handle issue that zod reported
|
|
42
|
+
*/
|
|
43
|
+
handleIssues: (issues: ReadonlyArray<ZodIssue>) => (prev: FullFormState<StateBeforeValidation>) => FullFormState<StateBeforeValidation>;
|
|
44
|
+
/**
|
|
45
|
+
* You can handle server side or other external error via this action.
|
|
46
|
+
*/
|
|
47
|
+
handleError: (reducer: (prev: FormErrors<StateBeforeValidation>) => FormErrors<StateBeforeValidation>) => (prev: FullFormState<StateBeforeValidation>) => FullFormState<StateBeforeValidation>;
|
|
48
|
+
/**
|
|
49
|
+
*/
|
|
50
|
+
handleChange: HandleChangeAction<StateBeforeValidation, (prev: FullFormState<StateBeforeValidation>) => FullFormState<StateBeforeValidation>>;
|
|
51
|
+
reduceFormState: (reducer: (prevValue: StateBeforeValidation) => StateBeforeValidation) => (prev: FullFormState<StateBeforeValidation>) => FullFormState<StateBeforeValidation>;
|
|
52
|
+
handleBulkChange: (setter: (prev: StateBeforeValidation) => Partial<StateBeforeValidation>) => (prev: FullFormState<StateBeforeValidation>) => FullFormState<StateBeforeValidation>;
|
|
53
|
+
};
|
|
54
|
+
declare type ActionsCanBePublic<State extends StateRestriction> = {
|
|
55
|
+
handleChange: HandleChangeAction<State>;
|
|
56
|
+
handleBulkChange: (setter: (prev: State) => Partial<State>) => void;
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* Restricted type interface of the form store
|
|
60
|
+
* TODO: more restriction
|
|
61
|
+
*/
|
|
62
|
+
export declare type Controller<State extends StateRestriction> = {
|
|
63
|
+
useInitialize: (initialValue: Partial<State>) => void;
|
|
64
|
+
useSelector: <R>(selector: (state: FullFormState<State>) => R, isEqual?: (prev: R, current: R) => boolean) => R;
|
|
65
|
+
actions: ActionsCanBePublic<State>;
|
|
66
|
+
components: {
|
|
67
|
+
Field: <Name extends keyof State>(props: FieldProps<State, Name>) => ReactElement<any, any> | null;
|
|
68
|
+
Slicer: <Selected extends ReadonlyArray<unknown>>(props: SliceProps<State, Selected>) => ReactElement<any, any> | null;
|
|
69
|
+
};
|
|
70
|
+
};
|
|
71
|
+
declare type FormHook<State extends StateRestriction, Schema extends ZodType<any, any, any>> = {
|
|
72
|
+
controller: Controller<State>;
|
|
73
|
+
handleSubmit: (handler: (e: BaseSyntheticEvent) => (val: {
|
|
74
|
+
success: true;
|
|
75
|
+
data: zodInfer<Schema>;
|
|
76
|
+
error: undefined;
|
|
77
|
+
} | {
|
|
78
|
+
success: false;
|
|
79
|
+
error: ReadonlyArray<ZodIssue>;
|
|
80
|
+
}) => void | Promise<void>) => (e: BaseSyntheticEvent) => void;
|
|
81
|
+
api: Subscriber<FullFormState<State>, FormControllerBehavior<State>>;
|
|
82
|
+
} & Controller<State>;
|
|
83
|
+
/**
|
|
84
|
+
* @typeParam InitialState should be shallow
|
|
85
|
+
*/
|
|
86
|
+
export declare function createFormHook<StateBeforeValidation, Schema extends ZodType<any, any, any>>(initialState: StateBeforeValidation, schema: Schema): FormHook<StateBeforeValidation, Schema>;
|
|
87
|
+
export declare function useFormSlice<State extends StateRestriction, Sliced>(formController: Controller<State>, selector: (s: FullFormState<State>) => Sliced): readonly [Sliced, ActionsCanBePublic<State>];
|
|
88
|
+
export {};
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { createStore } from 'nozuchi';
|
|
2
|
+
import { createElement, Fragment, useEffect, useMemo } from 'react';
|
|
3
|
+
import { isChangeEvent } from './utils';
|
|
4
|
+
const initialFormState = Object.freeze({ isDirty: false, isValid: false });
|
|
5
|
+
function createFormStore(initialState, schema) {
|
|
6
|
+
const initialErrors = Object.freeze(Object.keys(initialState).reduce((buf, k) => {
|
|
7
|
+
buf[k] = null;
|
|
8
|
+
return buf;
|
|
9
|
+
}, {}));
|
|
10
|
+
const fullInitialState = Object.freeze({
|
|
11
|
+
...initialFormState,
|
|
12
|
+
value: { ...initialState },
|
|
13
|
+
errors: { ...initialErrors },
|
|
14
|
+
});
|
|
15
|
+
const withValidation = (prev, newValue) => {
|
|
16
|
+
const result = schema.safeParse(newValue);
|
|
17
|
+
if (result.success) {
|
|
18
|
+
return { ...prev, isDirty: true, isValid: true, value: newValue, errors: initialErrors };
|
|
19
|
+
}
|
|
20
|
+
const newErrors = result.error.issues.reduce((buf, iss) => {
|
|
21
|
+
// TODO: should support nested value?
|
|
22
|
+
const shallowPath = iss.path[0];
|
|
23
|
+
if (shallowPath === undefined) {
|
|
24
|
+
return buf;
|
|
25
|
+
}
|
|
26
|
+
buf[shallowPath] = iss.message;
|
|
27
|
+
return buf;
|
|
28
|
+
}, {});
|
|
29
|
+
return { ...prev, isDirty: true, isValid: false, errors: newErrors, value: newValue };
|
|
30
|
+
};
|
|
31
|
+
return createStore(fullInitialState, {
|
|
32
|
+
peek: (eyeball) => (prev) => {
|
|
33
|
+
eyeball(prev);
|
|
34
|
+
return prev;
|
|
35
|
+
},
|
|
36
|
+
reset: () => () => fullInitialState,
|
|
37
|
+
initializeForm: (initialVal) => (prev) => ({
|
|
38
|
+
...prev,
|
|
39
|
+
value: { ...prev.value, ...initialVal },
|
|
40
|
+
errors: { ...initialErrors },
|
|
41
|
+
}),
|
|
42
|
+
handleIssues: (issues) => (prev) => {
|
|
43
|
+
// FIXME: handle duplication
|
|
44
|
+
const newErrors = issues.reduce((buf, iss) => {
|
|
45
|
+
const shallowPath = iss.path[0];
|
|
46
|
+
if (shallowPath === undefined) {
|
|
47
|
+
return buf;
|
|
48
|
+
}
|
|
49
|
+
buf[shallowPath] = iss.message;
|
|
50
|
+
return buf;
|
|
51
|
+
}, {});
|
|
52
|
+
return { ...prev, isDirty: true, isValid: false, errors: newErrors };
|
|
53
|
+
},
|
|
54
|
+
handleError: (reducer) => (prev) => {
|
|
55
|
+
return { ...prev, errors: reducer(prev.errors) };
|
|
56
|
+
},
|
|
57
|
+
handleChange: (name, value) => (prev) => {
|
|
58
|
+
if (name in prev.value === false) {
|
|
59
|
+
return prev;
|
|
60
|
+
}
|
|
61
|
+
const newValue = { ...prev.value, [name]: value };
|
|
62
|
+
return withValidation(prev, newValue);
|
|
63
|
+
},
|
|
64
|
+
reduceFormState: (reducer) => (prev) => {
|
|
65
|
+
const reducedState = reducer(prev.value);
|
|
66
|
+
return withValidation(prev, reducedState);
|
|
67
|
+
},
|
|
68
|
+
handleBulkChange: (setter) => (prev) => {
|
|
69
|
+
const mergedNewState = { ...prev.value, ...setter(prev.value) };
|
|
70
|
+
return withValidation(prev, mergedNewState);
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* @typeParam InitialState should be shallow
|
|
76
|
+
*/
|
|
77
|
+
export function createFormHook(initialState, schema) {
|
|
78
|
+
const store = createFormStore(initialState, schema);
|
|
79
|
+
const formHook = {
|
|
80
|
+
useInitialize: (initialValue) => {
|
|
81
|
+
useEffect(() => {
|
|
82
|
+
store.actions.initializeForm(initialValue);
|
|
83
|
+
}, []);
|
|
84
|
+
},
|
|
85
|
+
useSelector: store.useSelector,
|
|
86
|
+
handleSubmit: (handler) => {
|
|
87
|
+
return (e) => {
|
|
88
|
+
const eventHandled = handler(e);
|
|
89
|
+
const value = store.getState().value;
|
|
90
|
+
return schema.safeParseAsync(value).then((result) => {
|
|
91
|
+
// FIXME: hook formを捨ててzodのバージョンをあげればこの辺に型をつけられる
|
|
92
|
+
if (result.success) {
|
|
93
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
94
|
+
return eventHandled({ success: true, data: result.data, error: undefined });
|
|
95
|
+
}
|
|
96
|
+
void eventHandled({ success: false, error: result.error.issues });
|
|
97
|
+
store.actions.handleIssues(result.error.issues);
|
|
98
|
+
});
|
|
99
|
+
};
|
|
100
|
+
},
|
|
101
|
+
components: {
|
|
102
|
+
Slicer(props) {
|
|
103
|
+
const { selector, children } = props;
|
|
104
|
+
const value = formHook.useSelector(selector);
|
|
105
|
+
return useMemo(() => {
|
|
106
|
+
const renderContent = children({ handleChange: formHook.actions.handleChange }, ...value);
|
|
107
|
+
return createElement(Fragment, {}, renderContent);
|
|
108
|
+
}, value);
|
|
109
|
+
},
|
|
110
|
+
Field(props) {
|
|
111
|
+
const { name, children } = props;
|
|
112
|
+
const [value, errMsg] = store.useSelector((s) => [s.value[name], s.errors[name]]);
|
|
113
|
+
return useMemo(() => {
|
|
114
|
+
const renderContent = children({
|
|
115
|
+
name,
|
|
116
|
+
value,
|
|
117
|
+
onChange: (name_or_event, value_or_none) => {
|
|
118
|
+
if (typeof name_or_event === 'string') {
|
|
119
|
+
// assumes name is valid if it's string
|
|
120
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
121
|
+
return store.actions.handleChange(name_or_event, value_or_none);
|
|
122
|
+
}
|
|
123
|
+
if (isChangeEvent(name_or_event)) {
|
|
124
|
+
const target = name_or_event.currentTarget || name_or_event.target;
|
|
125
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access
|
|
126
|
+
return store.actions.handleChange(target.name, target.value);
|
|
127
|
+
}
|
|
128
|
+
throw new Error('`handleChange` handles unexpected formed object.');
|
|
129
|
+
},
|
|
130
|
+
}, value, errMsg);
|
|
131
|
+
return createElement(Fragment, {}, renderContent);
|
|
132
|
+
}, [value, errMsg]);
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
actions: store.actions,
|
|
136
|
+
};
|
|
137
|
+
return {
|
|
138
|
+
controller: formHook,
|
|
139
|
+
api: store,
|
|
140
|
+
...formHook,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
export function useFormSlice(formController, selector) {
|
|
144
|
+
const sliced = formController.useSelector((s) => selector(s));
|
|
145
|
+
return [sliced, formController.actions];
|
|
146
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { render, screen, waitFor } from '@testing-library/react';
|
|
2
|
+
import userEvent from '@testing-library/user-event';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import { test, expect } from 'vitest';
|
|
5
|
+
import * as zod from 'zod';
|
|
6
|
+
import { createFormHook, useFormSlice } from './index';
|
|
7
|
+
const testSchema = zod.object({
|
|
8
|
+
name: zod.string(),
|
|
9
|
+
description: zod.string(),
|
|
10
|
+
age: zod.string().transform(val => Number(val)).refine(val => Number.isNaN(val) ? 0 : val),
|
|
11
|
+
marked: zod.boolean(),
|
|
12
|
+
});
|
|
13
|
+
const createTestHook = () => createFormHook({ name: '', description: '', age: '', marked: false }, testSchema);
|
|
14
|
+
const AgeInputComponent = (props) => {
|
|
15
|
+
const { controller } = props;
|
|
16
|
+
const [[age, errorMessage], { handleChange }] = useFormSlice(controller, s => [s.value.age, s.errors.age]);
|
|
17
|
+
return (React.createElement("div", null,
|
|
18
|
+
React.createElement("input", { "data-testid": "age-input", value: age, name: "age", onChange: (e) => {
|
|
19
|
+
handleChange('age', e.currentTarget.value);
|
|
20
|
+
} }),
|
|
21
|
+
React.createElement("span", { "data-testid": "age-error" }, errorMessage)));
|
|
22
|
+
};
|
|
23
|
+
// afterEach(cleanup);
|
|
24
|
+
test('useSelector can select the fields', async () => {
|
|
25
|
+
const TestFormHook = createTestHook();
|
|
26
|
+
const Top = () => {
|
|
27
|
+
return (React.createElement("div", null,
|
|
28
|
+
React.createElement(AgeInputComponent, { controller: TestFormHook.controller })));
|
|
29
|
+
};
|
|
30
|
+
render(React.createElement(Top, null));
|
|
31
|
+
const INPUT_AGE = '4';
|
|
32
|
+
// fireEvent.change(screen.getByTestId('age-input') , {target: {value: INPUT_AGE}})
|
|
33
|
+
// calling `userEvent` in here will cause race condition...
|
|
34
|
+
await userEvent.type(screen.getByTestId('age-input'), INPUT_AGE);
|
|
35
|
+
// TestFormHook.actions.handleBulkChange(prev => ({...prev, name: 'test', description: 'desc'}));
|
|
36
|
+
await waitFor(() => {
|
|
37
|
+
const ageInput = screen.getByTestId('age-input');
|
|
38
|
+
expect(ageInput.value).toBe(INPUT_AGE);
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
test('zod parse value before submit', async () => {
|
|
42
|
+
const TestFormHook = createTestHook();
|
|
43
|
+
const INPUT_AGE = '9';
|
|
44
|
+
const PARSED_RESULT = Number(INPUT_AGE);
|
|
45
|
+
const handleSubmitTester = TestFormHook.handleSubmit((e) => (result) => {
|
|
46
|
+
if (result.success) {
|
|
47
|
+
expect(result.data.age).toBe(PARSED_RESULT);
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
expect('should not called').toBe(result.error);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
const Top = () => {
|
|
54
|
+
return (React.createElement("div", null,
|
|
55
|
+
React.createElement(AgeInputComponent, { controller: TestFormHook }),
|
|
56
|
+
React.createElement("button", { "data-testid": "submit-button", onClick: handleSubmitTester })));
|
|
57
|
+
};
|
|
58
|
+
render(React.createElement(Top, null));
|
|
59
|
+
const ageInput = screen.getByTestId('age-input');
|
|
60
|
+
await userEvent.type(ageInput, INPUT_AGE);
|
|
61
|
+
await waitFor(() => {
|
|
62
|
+
const ageInput = screen.getByTestId('age-input');
|
|
63
|
+
expect(ageInput.value).toBe(INPUT_AGE);
|
|
64
|
+
});
|
|
65
|
+
const submitButton = screen.getByTestId('submit-button');
|
|
66
|
+
await userEvent.click(submitButton);
|
|
67
|
+
await waitFor(() => {
|
|
68
|
+
console.log('waiting');
|
|
69
|
+
TestFormHook.api.actions.peek((s) => {
|
|
70
|
+
expect(s.value.age).toBe(INPUT_AGE);
|
|
71
|
+
expect(s.errors.age).toBeNull();
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
});
|
package/dist/utils.d.ts
ADDED
package/dist/utils.js
ADDED
package/package.json
CHANGED
|
@@ -1,19 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mayoiga",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
5
|
-
"main": "
|
|
6
|
-
"types": "
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Type safe hook based form library for React",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
7
|
"files": [
|
|
8
|
-
"
|
|
8
|
+
"dist"
|
|
9
9
|
],
|
|
10
10
|
"scripts": {
|
|
11
|
-
"
|
|
12
|
-
"
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
"
|
|
11
|
+
"clean": "rm -rf dist",
|
|
12
|
+
"test": "vitest run",
|
|
13
|
+
"prebuild": "npm run clean",
|
|
14
|
+
"build": "tsc",
|
|
15
|
+
"check:style": "prettier --check 'src/**/*.{js,ts}'",
|
|
16
|
+
"check:lint": "eslint 'src/**/*.{ts,tsx}'",
|
|
17
|
+
"check-all": "npm run check:style && npm run check:lint && npm run build --noEmit && npm run test",
|
|
18
|
+
"fix-all": "npm run check:style --write && npm run check:lint --fix",
|
|
19
|
+
"prepublish": "npm run check-all && npm run build"
|
|
17
20
|
},
|
|
18
21
|
"repository": {
|
|
19
22
|
"type": "git",
|
|
@@ -26,41 +29,36 @@
|
|
|
26
29
|
"validation",
|
|
27
30
|
"state"
|
|
28
31
|
],
|
|
29
|
-
"author": "
|
|
32
|
+
"author": "OGURA Daiki",
|
|
30
33
|
"license": "MIT",
|
|
31
34
|
"bugs": {
|
|
32
35
|
"url": "https://github.com/hachibeeDI/mayoiga/issues"
|
|
33
36
|
},
|
|
34
37
|
"homepage": "https://github.com/hachibeeDI/mayoiga#readme",
|
|
35
|
-
"devDependencies": {
|
|
36
|
-
"@babel/cli": "^7.2.3",
|
|
37
|
-
"@babel/core": "^7.4.0",
|
|
38
|
-
"@babel/plugin-proposal-class-properties": "^7.4.0",
|
|
39
|
-
"@babel/plugin-proposal-object-rest-spread": "^7.4.0",
|
|
40
|
-
"@babel/preset-env": "^7.4.1",
|
|
41
|
-
"@babel/preset-react": "^7.0.0",
|
|
42
|
-
"@babel/preset-typescript": "^7.3.3",
|
|
43
|
-
"@types/node": "^11.11.4",
|
|
44
|
-
"@types/react": "^16.8.8",
|
|
45
|
-
"@types/react-dom": "^16.8.2",
|
|
46
|
-
"babel-loader": "^8.0.5",
|
|
47
|
-
"prettier": "^1.16.4",
|
|
48
|
-
"react": "^16.8.6",
|
|
49
|
-
"react-dom": "^16.8.6",
|
|
50
|
-
"tslint": "^5.14.0",
|
|
51
|
-
"tslint-config-prettier": "^1.18.0",
|
|
52
|
-
"tslint-immutable": "^5.5.1",
|
|
53
|
-
"tslint-plugin-prettier": "^2.0.1",
|
|
54
|
-
"tslint-react": "^3.6.0",
|
|
55
|
-
"typedoc": "^0.14.2",
|
|
56
|
-
"typescript": "^3.3.4000",
|
|
57
|
-
"typescript-eslint-parser": "^22.0.0",
|
|
58
|
-
"webpack": "^4.29.6",
|
|
59
|
-
"webpack-cli": "^3.3.0",
|
|
60
|
-
"webpack-dev-server": "^3.2.1"
|
|
61
|
-
},
|
|
62
38
|
"peerDependencies": {
|
|
63
|
-
"
|
|
39
|
+
"zod": "^3.14.4"
|
|
64
40
|
},
|
|
65
|
-
"dependencies": {
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"nozuchi": "^0.0.3"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@testing-library/react": "^13.0.0",
|
|
46
|
+
"@testing-library/user-event": "^14.1.1",
|
|
47
|
+
"@types/node": "^17.0.23",
|
|
48
|
+
"@types/react": "^18.0.1",
|
|
49
|
+
"@types/use-sync-external-store": "^0.0.3",
|
|
50
|
+
"@typescript-eslint/eslint-plugin": "^5.20.0",
|
|
51
|
+
"@typescript-eslint/parser": "^5.20.0",
|
|
52
|
+
"eslint": "^8.13.0",
|
|
53
|
+
"eslint-plugin-import": "^2.26.0",
|
|
54
|
+
"eslint-plugin-testing-library": "^5.3.1",
|
|
55
|
+
"jsdom": "^19.0.0",
|
|
56
|
+
"prettier": "^2.6.2",
|
|
57
|
+
"react": "^18.0.0",
|
|
58
|
+
"react-dom": "^18.0.0",
|
|
59
|
+
"typescript": "^4.6.3",
|
|
60
|
+
"use-sync-external-store": "^1.0.0",
|
|
61
|
+
"vitest": "^0.9.3",
|
|
62
|
+
"zod": "^3.14.4"
|
|
63
|
+
}
|
|
66
64
|
}
|
package/lib/actions.d.ts
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
export declare type FSA<S> = {
|
|
2
|
-
type: 'SWAP';
|
|
3
|
-
payload: S;
|
|
4
|
-
} | {
|
|
5
|
-
type: 'SET_STATE';
|
|
6
|
-
payload: Partial<S>;
|
|
7
|
-
} | {
|
|
8
|
-
type: 'CHANGE';
|
|
9
|
-
payload: {
|
|
10
|
-
name: keyof S;
|
|
11
|
-
value: S[keyof S];
|
|
12
|
-
};
|
|
13
|
-
} | {
|
|
14
|
-
type: 'SUBMIT';
|
|
15
|
-
payload: {};
|
|
16
|
-
} | {
|
|
17
|
-
type: 'ERROR';
|
|
18
|
-
payload: {
|
|
19
|
-
name: keyof S;
|
|
20
|
-
value: ReadonlyArray<string>;
|
|
21
|
-
};
|
|
22
|
-
};
|
|
23
|
-
export declare type FormReducerActionTypes = FSA<any>['type'];
|
|
24
|
-
export declare const swap: <S>(newInitialState: S) => FSA<S>;
|
|
25
|
-
export declare const setNewState: <S>(newState: Partial<S>) => FSA<S>;
|
|
26
|
-
export declare const changeField: <S>(name: keyof S, value: S[keyof S]) => FSA<S>;
|
|
27
|
-
export declare const submitValue: <S>(state: S) => FSA<S>;
|
|
28
|
-
export declare const sendErrors: <S>(name: keyof S, errors: ReadonlyArray<string>) => FSA<S>;
|
package/lib/actions.js
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.swap = (newInitialState) => ({
|
|
4
|
-
type: 'SWAP',
|
|
5
|
-
payload: newInitialState,
|
|
6
|
-
});
|
|
7
|
-
exports.setNewState = (newState) => ({
|
|
8
|
-
type: 'SET_STATE',
|
|
9
|
-
payload: newState,
|
|
10
|
-
});
|
|
11
|
-
exports.changeField = (name, value) => ({
|
|
12
|
-
type: 'CHANGE',
|
|
13
|
-
payload: {
|
|
14
|
-
name,
|
|
15
|
-
value,
|
|
16
|
-
},
|
|
17
|
-
});
|
|
18
|
-
exports.submitValue = (state) => ({
|
|
19
|
-
type: 'SUBMIT',
|
|
20
|
-
payload: {},
|
|
21
|
-
});
|
|
22
|
-
exports.sendErrors = (name, errors) => ({
|
|
23
|
-
type: 'ERROR',
|
|
24
|
-
payload: {
|
|
25
|
-
name,
|
|
26
|
-
value: errors,
|
|
27
|
-
},
|
|
28
|
-
});
|
|
29
|
-
//# sourceMappingURL=actions.js.map
|
package/lib/actions.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"actions.js","sourceRoot":"","sources":["../src/actions.ts"],"names":[],"mappings":";;AA6Ba,QAAA,IAAI,GAAG,CAAI,eAAkB,EAAU,EAAE,CAAC,CAAC;IACtD,IAAI,EAAE,MAAM;IACZ,OAAO,EAAE,eAAe;CACzB,CAAC,CAAC;AAEU,QAAA,WAAW,GAAG,CAAI,QAAoB,EAAU,EAAE,CAAC,CAAC;IAC/D,IAAI,EAAE,WAAW;IACjB,OAAO,EAAE,QAAQ;CAClB,CAAC,CAAC;AAEU,QAAA,WAAW,GAAG,CAAI,IAAa,EAAE,KAAiB,EAAU,EAAE,CAAC,CAAC;IAC3E,IAAI,EAAE,QAAQ;IACd,OAAO,EAAE;QACP,IAAI;QACJ,KAAK;KACN;CACF,CAAC,CAAC;AAEU,QAAA,WAAW,GAAG,CAAI,KAAQ,EAAU,EAAE,CAAC,CAAC;IACnD,IAAI,EAAE,QAAQ;IACd,OAAO,EAAE,EAAE;CACZ,CAAC,CAAC;AAEU,QAAA,UAAU,GAAG,CAAI,IAAa,EAAE,MAA6B,EAAU,EAAE,CAAC,CAAC;IACtF,IAAI,EAAE,OAAO;IACb,OAAO,EAAE;QACP,IAAI;QACJ,KAAK,EAAE,MAAM;KACd;CACF,CAAC,CAAC"}
|
package/lib/forms.d.ts
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* This is just a sample implementation.
|
|
3
|
-
* You can make to fit any component library that you are using.
|
|
4
|
-
*
|
|
5
|
-
*/
|
|
6
|
-
/// <reference types="react" />
|
|
7
|
-
import { InputProtocol } from './inputProtocol';
|
|
8
|
-
export declare function MappedInputFactory(mapInputToState?: (val: string) => any /** FIXME: fmmmmmm */): <S, Name extends keyof S>(props: InputProtocol<S, Name, undefined>) => JSX.Element;
|
|
9
|
-
export declare const Input: <S, Name extends keyof S>(props: InputProtocol<S, Name, undefined>) => JSX.Element;
|
|
10
|
-
export declare const NumberInput: <S, Name extends keyof S>(props: InputProtocol<S, Name, undefined>) => JSX.Element;
|
|
11
|
-
export declare function MappedRadioFactory(candidates: ReadonlyArray<{
|
|
12
|
-
label: string;
|
|
13
|
-
value: any;
|
|
14
|
-
}>,
|
|
15
|
-
/** FIXME: fmmmmmm, I think I can write infer which returns `never` if type is invalid but... */
|
|
16
|
-
mapInputToState?: (val: string) => any): <S, Name extends keyof S>(props: InputProtocol<S, Name, undefined>) => JSX.Element;
|
|
17
|
-
export declare function MappedSelectFactory(options: ReadonlyArray<{
|
|
18
|
-
label: string;
|
|
19
|
-
value: any;
|
|
20
|
-
disabled?: boolean;
|
|
21
|
-
}>, mapInputToState?: (val: string) => any): <S, Name extends keyof S>(props: InputProtocol<S, Name, undefined>) => JSX.Element;
|
package/lib/forms.js
DELETED
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* This is just a sample implementation.
|
|
4
|
-
* You can make to fit any component library that you are using.
|
|
5
|
-
*
|
|
6
|
-
*/
|
|
7
|
-
var __rest = (this && this.__rest) || function (s, e) {
|
|
8
|
-
var t = {};
|
|
9
|
-
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
10
|
-
t[p] = s[p];
|
|
11
|
-
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
12
|
-
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) if (e.indexOf(p[i]) < 0)
|
|
13
|
-
t[p[i]] = s[p[i]];
|
|
14
|
-
return t;
|
|
15
|
-
};
|
|
16
|
-
var __importStar = (this && this.__importStar) || function (mod) {
|
|
17
|
-
if (mod && mod.__esModule) return mod;
|
|
18
|
-
var result = {};
|
|
19
|
-
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
|
|
20
|
-
result["default"] = mod;
|
|
21
|
-
return result;
|
|
22
|
-
};
|
|
23
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
|
-
const React = __importStar(require("react"));
|
|
25
|
-
const react_1 = require("react");
|
|
26
|
-
// TODO: mapStateToValue (val: S[Name]) => string
|
|
27
|
-
function MappedInputFactory(mapInputToState = val => val /** FIXME: fmmmmmm */) {
|
|
28
|
-
return (props) => {
|
|
29
|
-
const { name, value, onChange, errors, touched, delegatedProps = {} } = props, restProps = __rest(props, ["name", "value", "onChange", "errors", "touched", "delegatedProps"]);
|
|
30
|
-
const handleChange = react_1.useCallback((e) => {
|
|
31
|
-
const value = e.currentTarget.value;
|
|
32
|
-
onChange(name, mapInputToState(value));
|
|
33
|
-
}, [onChange]);
|
|
34
|
-
return (React.createElement(React.Fragment, null,
|
|
35
|
-
React.createElement("input", Object.assign({}, restProps, delegatedProps, { value: value ? value.toString() : '', name: name, onChange: handleChange })),
|
|
36
|
-
touched && errors.length !== 0 && React.createElement("div", { style: { color: 'red' } }, errors[0])));
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
|
-
exports.MappedInputFactory = MappedInputFactory;
|
|
40
|
-
exports.Input = MappedInputFactory();
|
|
41
|
-
exports.NumberInput = MappedInputFactory(val => parseInt(val, 10) || 0);
|
|
42
|
-
function MappedRadioFactory(candidates,
|
|
43
|
-
/** FIXME: fmmmmmm, I think I can write infer which returns `never` if type is invalid but... */
|
|
44
|
-
mapInputToState = val => val) {
|
|
45
|
-
return (props) => {
|
|
46
|
-
const { name, onChange, errors, touched, delegatedProps = {} } = props, restProps = __rest(props, ["name", "onChange", "errors", "touched", "delegatedProps"]);
|
|
47
|
-
const handleChange = react_1.useCallback((e) => {
|
|
48
|
-
const value = e.currentTarget.value;
|
|
49
|
-
onChange(name, mapInputToState(value));
|
|
50
|
-
}, [onChange]);
|
|
51
|
-
return (React.createElement(React.Fragment, null,
|
|
52
|
-
candidates.map(({ label, value }) => (React.createElement("label", { key: value },
|
|
53
|
-
React.createElement("input", Object.assign({}, restProps, delegatedProps, { type: "radio", name: name, value: value, checked: value === props.value, onChange: handleChange })),
|
|
54
|
-
label))),
|
|
55
|
-
touched && errors.length !== 0 && React.createElement("div", { style: { color: 'red' } }, errors[0])));
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
exports.MappedRadioFactory = MappedRadioFactory;
|
|
59
|
-
function MappedSelectFactory(options, mapInputToState = val => val) {
|
|
60
|
-
return (props) => {
|
|
61
|
-
const { value, name, onChange, errors, touched, delegatedProps = {} } = props, restProps = __rest(props, ["value", "name", "onChange", "errors", "touched", "delegatedProps"]);
|
|
62
|
-
const handleChange = react_1.useCallback((e) => {
|
|
63
|
-
const value = e.currentTarget.value;
|
|
64
|
-
onChange(name, mapInputToState(value));
|
|
65
|
-
}, [onChange]);
|
|
66
|
-
return (React.createElement(React.Fragment, null,
|
|
67
|
-
React.createElement("select", Object.assign({}, restProps, delegatedProps, { name: name, value: value ? value.toString() : '', onChange: handleChange }),
|
|
68
|
-
React.createElement("option", { value: "", disabled: true }, "Please select"),
|
|
69
|
-
options.map(({ label, value, disabled }) => (React.createElement("option", { key: value, value: value, disabled: disabled }, label)))),
|
|
70
|
-
touched && errors.length !== 0 && React.createElement("div", { style: { color: 'red' } }, errors[0])));
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
exports.MappedSelectFactory = MappedSelectFactory;
|
|
74
|
-
//# sourceMappingURL=forms.js.map
|
package/lib/forms.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"forms.js","sourceRoot":"","sources":["../src/forms.tsx"],"names":[],"mappings":"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"}
|
package/lib/index.d.ts
DELETED
package/lib/index.js
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const mayoiga_1 = require("./mayoiga");
|
|
4
|
-
exports.useForm = mayoiga_1.useForm;
|
|
5
|
-
exports.useFormState = mayoiga_1.useFormState;
|
|
6
|
-
exports.createFormScope = mayoiga_1.createFormScope;
|
|
7
|
-
//# sourceMappingURL=index.js.map
|
package/lib/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.tsx"],"names":[],"mappings":";;AAAA,uCAAiE;AAGzD,kBAHA,iBAAO,CAGA;AAAE,uBAHA,sBAAY,CAGA;AAAE,0BAHA,yBAAe,CAGA"}
|
package/lib/inputProtocol.d.ts
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { InputHTMLAttributes } from 'react';
|
|
2
|
-
declare type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
|
|
3
|
-
declare type MayoigaConnectedInputProtocol<S, Name extends keyof S, DelegatedProps> = {
|
|
4
|
-
name: Name;
|
|
5
|
-
value: S[Name];
|
|
6
|
-
/** Errors which generated by mayoiga validation */
|
|
7
|
-
errors: ReadonlyArray<string>;
|
|
8
|
-
/** Validation is running even if it is first renderd. `touched` can be useful if you would like to suppress error indication until user touched the input. */
|
|
9
|
-
touched: boolean;
|
|
10
|
-
onChange(name: Name, value: S[Name]): void;
|
|
11
|
-
/**
|
|
12
|
-
* Props which passed via `componentProps` of {@link FieldProps}.
|
|
13
|
-
*/
|
|
14
|
-
delegatedProps: DelegatedProps;
|
|
15
|
-
};
|
|
16
|
-
export declare type InputProtocol<S, Name extends keyof S, DelegatedProps> = MayoigaConnectedInputProtocol<S, Name, DelegatedProps> & Omit<InputHTMLAttributes<any>, 'name' | 'onChange' | 'value'>;
|
|
17
|
-
export {};
|
package/lib/inputProtocol.js
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
// TODO: How's the below?
|
|
4
|
-
// type Primitive = 'string' | 'number' | 'boolean';
|
|
5
|
-
// type FieldType<FT> = FT extends string
|
|
6
|
-
// ? 'string'
|
|
7
|
-
// : FT extends number
|
|
8
|
-
// ? 'number'
|
|
9
|
-
// : FT extends boolean
|
|
10
|
-
// ? 'boolean'
|
|
11
|
-
// : ((domVal: string) => FT);
|
|
12
|
-
// const PRIMITIVE_CONVERT_DEF = {
|
|
13
|
-
// string: (val: string) => val,
|
|
14
|
-
// number: (val: string) => parseInt(val, 10) || 0,
|
|
15
|
-
// boolean: (val: string) => val === 'true',
|
|
16
|
-
// };
|
|
17
|
-
// function fieldConverter<FT>(dataType: FieldType<FT>, value: string): FT {
|
|
18
|
-
// const def = (PRIMITIVE_CONVERT_DEF as any)[dataType as any];
|
|
19
|
-
// if (def) {
|
|
20
|
-
// return def(value);
|
|
21
|
-
// }
|
|
22
|
-
// return (dataType as any)(value);
|
|
23
|
-
// }
|
|
24
|
-
//# sourceMappingURL=inputProtocol.js.map
|
package/lib/inputProtocol.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"inputProtocol.js","sourceRoot":"","sources":["../src/inputProtocol.tsx"],"names":[],"mappings":";;AAsBA,yBAAyB;AACzB,oDAAoD;AACpD,yCAAyC;AACzC,eAAe;AACf,wBAAwB;AACxB,eAAe;AACf,yBAAyB;AACzB,gBAAgB;AAChB,gCAAgC;AAChC,kCAAkC;AAClC,kCAAkC;AAClC,qDAAqD;AACrD,8CAA8C;AAC9C,KAAK;AAEL,4EAA4E;AAC5E,iEAAiE;AACjE,eAAe;AACf,yBAAyB;AACzB,MAAM;AACN,qCAAqC;AACrC,IAAI"}
|
package/lib/mayoiga.d.ts
DELETED
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
import * as React from 'react';
|
|
2
|
-
import { Dispatch, Context, ReactNode, FC } from 'react';
|
|
3
|
-
import { FSA } from './actions';
|
|
4
|
-
import { Store } from './reducer';
|
|
5
|
-
import { InputProtocol } from './inputProtocol';
|
|
6
|
-
declare type MayoigaContextValue<S> = {
|
|
7
|
-
state: Store<S>;
|
|
8
|
-
dispatch: Dispatch<FSA<S>>;
|
|
9
|
-
};
|
|
10
|
-
/**
|
|
11
|
-
* Validator definition to pass to the each form fields.
|
|
12
|
-
*
|
|
13
|
-
* @param target The value which handled by the input field.
|
|
14
|
-
* @param record The form state itselfs. It is useful if you had to write validation depends on other fields.
|
|
15
|
-
* @returns It should return string if there is something wrong.
|
|
16
|
-
*/
|
|
17
|
-
declare type Validator<S, Name extends keyof S> = (target: S[Name], record: S) => undefined | string;
|
|
18
|
-
declare type FormProps<S> = {
|
|
19
|
-
onSubmit?(value: S): void;
|
|
20
|
-
children: ReactNode;
|
|
21
|
-
};
|
|
22
|
-
declare type FieldProps<S, Name extends keyof S, ComponentProps> = {
|
|
23
|
-
/** One on the name of field. Have to be `keyof State`. */
|
|
24
|
-
name: Name;
|
|
25
|
-
/** You might want to handle onChange event in case. */
|
|
26
|
-
onChange?(name: Name, value: S[Name]): void;
|
|
27
|
-
validations?: ReadonlyArray<Validator<S, Name>>;
|
|
28
|
-
/**
|
|
29
|
-
* Props to delegate to the input component.
|
|
30
|
-
* @example
|
|
31
|
-
* ```
|
|
32
|
-
*
|
|
33
|
-
* componentProps={{disabled: props.formState.age >= 20}}
|
|
34
|
-
* component={AlcoholCheckbox}
|
|
35
|
-
* ```
|
|
36
|
-
*/
|
|
37
|
-
componentProps?: ComponentProps;
|
|
38
|
-
/** Component which can handle the field of the form state. It should implement {@link InputProtocol}. */
|
|
39
|
-
component: FC<InputProtocol<S, Name, ComponentProps>>;
|
|
40
|
-
};
|
|
41
|
-
declare type MayoigaProps<S> = {
|
|
42
|
-
initialState: S;
|
|
43
|
-
onSubmit(hasErrors: boolean, value: S, formInfo: {
|
|
44
|
-
errors: Store<S>['errors'];
|
|
45
|
-
touched: boolean;
|
|
46
|
-
}): void;
|
|
47
|
-
};
|
|
48
|
-
/** @obsolete */
|
|
49
|
-
declare type ScopedComponentProps<S> = {
|
|
50
|
-
touched: boolean;
|
|
51
|
-
errors: Store<S>['errors'];
|
|
52
|
-
formState: S;
|
|
53
|
-
};
|
|
54
|
-
/**
|
|
55
|
-
* A function to create scope which allows to use `useForm` function to manage form value.
|
|
56
|
-
*
|
|
57
|
-
* @example
|
|
58
|
-
* ```
|
|
59
|
-
*
|
|
60
|
-
* type FormState = {name: '', age: 0};
|
|
61
|
-
* const {context, scope} = createFormScope<FormState>();
|
|
62
|
-
*
|
|
63
|
-
* const TheForm = scope(props => {
|
|
64
|
-
* const {Form, Field} = useForm(context);
|
|
65
|
-
* // ...
|
|
66
|
-
* ```
|
|
67
|
-
*
|
|
68
|
-
*/
|
|
69
|
-
export declare function createFormScope<S>(): {
|
|
70
|
-
context: React.Context<MayoigaContextValue<S>>;
|
|
71
|
-
scope: <OwnProps = {}>(ConnectedComponent: React.FunctionComponent<OwnProps & ScopedComponentProps<S>>) => (props: OwnProps & MayoigaProps<S>) => JSX.Element;
|
|
72
|
-
};
|
|
73
|
-
/**
|
|
74
|
-
* The Hook to summon type safe React form component.
|
|
75
|
-
* To know the props that Field can accept {@link FieldProps}.
|
|
76
|
-
*
|
|
77
|
-
* @example
|
|
78
|
-
* ```
|
|
79
|
-
*
|
|
80
|
-
* const TheForm = scope(props => {
|
|
81
|
-
* const {Form, Field} = useForm(context);
|
|
82
|
-
* return (
|
|
83
|
-
* <Form>
|
|
84
|
-
* <Field name="name" component={Input} validations={[required]} />
|
|
85
|
-
* <Field name="fieldNameHaven'tDefined" <= you will see compile error if you were specified the name haven't declared in the context type for initialState.
|
|
86
|
-
* </Form>
|
|
87
|
-
* );
|
|
88
|
-
* ```
|
|
89
|
-
*/
|
|
90
|
-
export declare function useForm<S>(formScope: Context<MayoigaContextValue<S>>): {
|
|
91
|
-
Form: React.MemoExoticComponent<(props: FormProps<S>) => JSX.Element>;
|
|
92
|
-
Field: <Name extends Extract<keyof S, string>, ComponentProps = undefined>(props: FieldProps<S, Name, ComponentProps>) => JSX.Element;
|
|
93
|
-
useFormState(): [Store<S>, (newState: Partial<S>) => void];
|
|
94
|
-
};
|
|
95
|
-
/**
|
|
96
|
-
* You can get current form state and also change it:
|
|
97
|
-
*
|
|
98
|
-
* ```
|
|
99
|
-
* const [{ errors, formData }, setFormState] = useFormState(context);
|
|
100
|
-
* ...
|
|
101
|
-
* onChange={(name, value) => {
|
|
102
|
-
* if (value) setFormState({otherFieldValue: ''});
|
|
103
|
-
* }}
|
|
104
|
-
* ```
|
|
105
|
-
*/
|
|
106
|
-
export declare function useFormState<S>(formScope: Context<MayoigaContextValue<S>>): [Store<S>, (newState: Partial<S>) => void];
|
|
107
|
-
export {};
|
package/lib/mayoiga.js
DELETED
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importStar = (this && this.__importStar) || function (mod) {
|
|
3
|
-
if (mod && mod.__esModule) return mod;
|
|
4
|
-
var result = {};
|
|
5
|
-
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
|
|
6
|
-
result["default"] = mod;
|
|
7
|
-
return result;
|
|
8
|
-
};
|
|
9
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
-
const React = __importStar(require("react"));
|
|
11
|
-
const react_1 = require("react");
|
|
12
|
-
const actions_1 = require("./actions");
|
|
13
|
-
const reducer_1 = require("./reducer");
|
|
14
|
-
const EMPTY_ERRORS = [];
|
|
15
|
-
// TODO: need `mapChanged(value: S): S;` ?
|
|
16
|
-
/**
|
|
17
|
-
* A function to create scope which allows to use `useForm` function to manage form value.
|
|
18
|
-
*
|
|
19
|
-
* @example
|
|
20
|
-
* ```
|
|
21
|
-
*
|
|
22
|
-
* type FormState = {name: '', age: 0};
|
|
23
|
-
* const {context, scope} = createFormScope<FormState>();
|
|
24
|
-
*
|
|
25
|
-
* const TheForm = scope(props => {
|
|
26
|
-
* const {Form, Field} = useForm(context);
|
|
27
|
-
* // ...
|
|
28
|
-
* ```
|
|
29
|
-
*
|
|
30
|
-
*/
|
|
31
|
-
function createFormScope() {
|
|
32
|
-
const Ctx = react_1.createContext(null);
|
|
33
|
-
return {
|
|
34
|
-
context: Ctx,
|
|
35
|
-
scope: function (ConnectedComponent) {
|
|
36
|
-
return (props) => {
|
|
37
|
-
const { initialState, onSubmit } = props;
|
|
38
|
-
const [state, dispatch] = reducer_1.useFormReducer(initialState, onSubmit);
|
|
39
|
-
// TODO: I assumed useReducer is re-render if initialState was changed but doesn't. Need investigation though, I'll address by the useEffect for the time being.
|
|
40
|
-
react_1.useEffect(() => dispatch(actions_1.swap(initialState)), [initialState]);
|
|
41
|
-
const touchedAll = Object.values(state.touched).every(v => v);
|
|
42
|
-
return (React.createElement(Ctx.Provider, { value: { state, dispatch } },
|
|
43
|
-
React.createElement(ConnectedComponent, Object.assign({}, props, { formState: state.formData, touched: touchedAll, errors: state.errors }))));
|
|
44
|
-
};
|
|
45
|
-
},
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
exports.createFormScope = createFormScope;
|
|
49
|
-
/**
|
|
50
|
-
* The Hook to summon type safe React form component.
|
|
51
|
-
* To know the props that Field can accept {@link FieldProps}.
|
|
52
|
-
*
|
|
53
|
-
* @example
|
|
54
|
-
* ```
|
|
55
|
-
*
|
|
56
|
-
* const TheForm = scope(props => {
|
|
57
|
-
* const {Form, Field} = useForm(context);
|
|
58
|
-
* return (
|
|
59
|
-
* <Form>
|
|
60
|
-
* <Field name="name" component={Input} validations={[required]} />
|
|
61
|
-
* <Field name="fieldNameHaven'tDefined" <= you will see compile error if you were specified the name haven't declared in the context type for initialState.
|
|
62
|
-
* </Form>
|
|
63
|
-
* );
|
|
64
|
-
* ```
|
|
65
|
-
*/
|
|
66
|
-
function useForm(formScope) {
|
|
67
|
-
return react_1.useMemo(() => ({
|
|
68
|
-
Form: react_1.memo((props) => {
|
|
69
|
-
const { state, dispatch } = react_1.useContext(formScope);
|
|
70
|
-
const { onSubmit } = props;
|
|
71
|
-
return (React.createElement("form", { onSubmit: e => {
|
|
72
|
-
e.preventDefault();
|
|
73
|
-
if (onSubmit) {
|
|
74
|
-
onSubmit(state.formData);
|
|
75
|
-
}
|
|
76
|
-
dispatch(actions_1.submitValue(state.formData));
|
|
77
|
-
} }, props.children));
|
|
78
|
-
}),
|
|
79
|
-
Field: (props) => {
|
|
80
|
-
const { state, dispatch } = react_1.useContext(formScope);
|
|
81
|
-
const { component, name, validations, onChange } = props;
|
|
82
|
-
const validate = (target) => {
|
|
83
|
-
if (validations !== undefined) {
|
|
84
|
-
const errors = validations.map(v => v(target, state.formData)).filter((result) => !!result);
|
|
85
|
-
dispatch(actions_1.sendErrors(name, errors.length === 0 ? [] : errors));
|
|
86
|
-
}
|
|
87
|
-
};
|
|
88
|
-
const handleChange = react_1.useCallback((name, value) => {
|
|
89
|
-
dispatch(actions_1.changeField(name, value));
|
|
90
|
-
if (onChange) {
|
|
91
|
-
onChange(name, value);
|
|
92
|
-
}
|
|
93
|
-
validate(value);
|
|
94
|
-
}, [name, state]);
|
|
95
|
-
const value = state.formData[name];
|
|
96
|
-
const errors = state.errors[name];
|
|
97
|
-
const touched = state.touched[name];
|
|
98
|
-
react_1.useEffect(() => {
|
|
99
|
-
// I'm not exactly sure why but we need it to accomplish dispatch action on mounted. :(
|
|
100
|
-
window.requestAnimationFrame(() => validate(value));
|
|
101
|
-
}, [state.formData]);
|
|
102
|
-
const Component = component;
|
|
103
|
-
return (React.createElement(Component, { name: name, value: value /** FIXME: */, onChange: handleChange, errors: errors, touched: touched, delegatedProps: props.componentProps }));
|
|
104
|
-
},
|
|
105
|
-
useFormState() {
|
|
106
|
-
const { state, dispatch } = react_1.useContext(formScope);
|
|
107
|
-
return [state, (newState) => dispatch(actions_1.setNewState(newState))];
|
|
108
|
-
},
|
|
109
|
-
}), [formScope]);
|
|
110
|
-
}
|
|
111
|
-
exports.useForm = useForm;
|
|
112
|
-
/**
|
|
113
|
-
* You can get current form state and also change it:
|
|
114
|
-
*
|
|
115
|
-
* ```
|
|
116
|
-
* const [{ errors, formData }, setFormState] = useFormState(context);
|
|
117
|
-
* ...
|
|
118
|
-
* onChange={(name, value) => {
|
|
119
|
-
* if (value) setFormState({otherFieldValue: ''});
|
|
120
|
-
* }}
|
|
121
|
-
* ```
|
|
122
|
-
*/
|
|
123
|
-
function useFormState(formScope) {
|
|
124
|
-
const { state, dispatch } = react_1.useContext(formScope);
|
|
125
|
-
return [state, (newState) => dispatch(actions_1.setNewState(newState))];
|
|
126
|
-
}
|
|
127
|
-
exports.useFormState = useFormState;
|
|
128
|
-
//# sourceMappingURL=mayoiga.js.map
|
package/lib/mayoiga.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"mayoiga.js","sourceRoot":"","sources":["../src/mayoiga.tsx"],"names":[],"mappings":"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"}
|
package/lib/reducer.d.ts
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
/// <reference types="react" />
|
|
2
|
-
import { FSA } from './actions';
|
|
3
|
-
declare type Errors<StateKeys extends string> = {
|
|
4
|
-
[K in StateKeys]: ReadonlyArray<string>;
|
|
5
|
-
};
|
|
6
|
-
declare type Touched<StateKeys extends string> = {
|
|
7
|
-
[K in StateKeys]: boolean;
|
|
8
|
-
};
|
|
9
|
-
export declare type Store<S extends {
|
|
10
|
-
[key: string]: any;
|
|
11
|
-
}> = {
|
|
12
|
-
errors: Errors<Extract<keyof S, string>>;
|
|
13
|
-
formData: S;
|
|
14
|
-
touched: Touched<Extract<keyof S, string>>;
|
|
15
|
-
};
|
|
16
|
-
export declare const useFormReducer: <S>(initialState: S, onSubmit: (hasErrors: boolean, value: S, formInfo: {
|
|
17
|
-
errors: Errors<Extract<keyof S, string>>;
|
|
18
|
-
touched: boolean;
|
|
19
|
-
}) => void) => [Store<S>, import("react").Dispatch<FSA<S>>];
|
|
20
|
-
export {};
|
package/lib/reducer.js
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const react_1 = require("react");
|
|
4
|
-
const createFormInitialState = (initialState) => {
|
|
5
|
-
const stateKeys = Object.keys(initialState);
|
|
6
|
-
return {
|
|
7
|
-
formData: initialState,
|
|
8
|
-
errors: stateKeys.reduce((buf, k) => {
|
|
9
|
-
// NOTE: because typedoc is using old version of TypeScript.
|
|
10
|
-
buf[k] = [];
|
|
11
|
-
return buf;
|
|
12
|
-
}, {}),
|
|
13
|
-
touched: stateKeys.reduce((buf, k) => {
|
|
14
|
-
// NOTE: same as above. Hey! should I make the code dirty because of document automation???
|
|
15
|
-
buf[k] = false;
|
|
16
|
-
return buf;
|
|
17
|
-
}, {}),
|
|
18
|
-
};
|
|
19
|
-
};
|
|
20
|
-
exports.useFormReducer = (initialState, onSubmit) => react_1.useReducer((state, action) => {
|
|
21
|
-
// TODO: logging functionality
|
|
22
|
-
// console.log(action);
|
|
23
|
-
switch (action.type) {
|
|
24
|
-
case 'SWAP': {
|
|
25
|
-
return createFormInitialState(action.payload);
|
|
26
|
-
}
|
|
27
|
-
case 'SET_STATE': {
|
|
28
|
-
return Object.assign({}, state, { formData: Object.assign({}, state.formData, action.payload) });
|
|
29
|
-
}
|
|
30
|
-
case 'CHANGE': {
|
|
31
|
-
const { name, value } = action.payload;
|
|
32
|
-
return Object.assign({}, state, { formData: Object.assign({}, state.formData, { [name]: value }), touched: Object.assign({}, state.touched, { [name]: true }) });
|
|
33
|
-
}
|
|
34
|
-
case 'SUBMIT': {
|
|
35
|
-
// FIXME: :thinking_face:
|
|
36
|
-
const haveTouchedAll = Object.values(state.touched).every(v => v);
|
|
37
|
-
const hasAnyError = Object.values(state.errors).some(errs => errs.length !== 0);
|
|
38
|
-
// I should repent my sin. I called side effect in reducer...
|
|
39
|
-
onSubmit(hasAnyError, state.formData, {
|
|
40
|
-
errors: state.errors,
|
|
41
|
-
touched: haveTouchedAll,
|
|
42
|
-
});
|
|
43
|
-
return state;
|
|
44
|
-
}
|
|
45
|
-
case 'ERROR': {
|
|
46
|
-
const { name, value } = action.payload;
|
|
47
|
-
return Object.assign({}, state, { errors: Object.assign({}, state.errors, { [name]: value }) });
|
|
48
|
-
}
|
|
49
|
-
default:
|
|
50
|
-
return state;
|
|
51
|
-
}
|
|
52
|
-
}, initialState, createFormInitialState);
|
|
53
|
-
//# sourceMappingURL=reducer.js.map
|
package/lib/reducer.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"reducer.js","sourceRoot":"","sources":["../src/reducer.ts"],"names":[],"mappings":";;AAAA,iCAAiC;AAejC,MAAM,sBAAsB,GAAG,CAAsB,YAAe,EAAE,EAAE;IACtE,MAAM,SAAS,GAAG,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;IAC5C,OAAO;QACL,QAAQ,EAAE,YAAY;QACtB,MAAM,EAAE,SAAS,CAAC,MAAM,CACtB,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE;YACT,4DAA4D;YAC3D,GAAW,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC;YACrB,OAAO,GAAG,CAAC;QACb,CAAC,EACD,EAAwB,CACzB;QACD,OAAO,EAAE,SAAS,CAAC,MAAM,CACvB,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE;YACT,2FAA2F;YAC1F,GAAW,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC;YACxB,OAAO,GAAG,CAAC;QACb,CAAC,EACD,EAAyB,CAC1B;KACF,CAAC;AACJ,CAAC,CAAC;AAEW,QAAA,cAAc,GAAG,CAC5B,YAAe,EACf,QAA0G,EAC1G,EAAE,CACF,kBAAU,CACR,CAAC,KAAe,EAAE,MAAc,EAAE,EAAE;IAClC,8BAA8B;IAC9B,uBAAuB;IACvB,QAAQ,MAAM,CAAC,IAAI,EAAE;QACnB,KAAK,MAAM,CAAC,CAAC;YACX,OAAO,sBAAsB,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;SAC/C;QACD,KAAK,WAAW,CAAC,CAAC;YAChB,yBACK,KAAK,IACR,QAAQ,oBACH,KAAK,CAAC,QAAQ,EACd,MAAM,CAAC,OAAO,KAEnB;SACH;QACD,KAAK,QAAQ,CAAC,CAAC;YACb,MAAM,EAAC,IAAI,EAAE,KAAK,EAAC,GAAG,MAAM,CAAC,OAAO,CAAC;YACrC,yBACK,KAAK,IACR,QAAQ,oBACH,KAAK,CAAC,QAAQ,IACjB,CAAC,IAAI,CAAC,EAAE,KAAK,KAEf,OAAO,oBACF,KAAK,CAAC,OAAO,IAChB,CAAC,IAAI,CAAC,EAAE,IAAI,OAEd;SACH;QACD,KAAK,QAAQ,CAAC,CAAC;YACb,yBAAyB;YACzB,MAAM,cAAc,GAAI,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,CAA4B,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;YAC9F,MAAM,WAAW,GAAI,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAA2B,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC;YAC3G,6DAA6D;YAC7D,QAAQ,CAAC,WAAW,EAAE,KAAK,CAAC,QAAQ,EAAE;gBACpC,MAAM,EAAE,KAAK,CAAC,MAAM;gBACpB,OAAO,EAAE,cAAc;aACxB,CAAC,CAAC;YACH,OAAO,KAAK,CAAC;SACd;QACD,KAAK,OAAO,CAAC,CAAC;YACZ,MAAM,EAAC,IAAI,EAAE,KAAK,EAAC,GAAG,MAAM,CAAC,OAAO,CAAC;YACrC,yBACK,KAAK,IACR,MAAM,oBACD,KAAK,CAAC,MAAM,IACf,CAAC,IAAI,CAAC,EAAE,KAAK,OAEf;SACH;QACD;YACE,OAAO,KAAK,CAAC;KAChB;AACH,CAAC,EACD,YAAY,EACZ,sBAAsB,CACvB,CAAC"}
|