mayoiga 0.0.22 → 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 CHANGED
@@ -1,3 +1,5 @@
1
+ [![Lint Check](https://github.com/hachibeeDI/mayoiga/actions/workflows/checker.yml/badge.svg)](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
- There is a short article which explains Mayoiga https://dev.to/hachibeedi/react-use-form-type-safe-react-hooks-based-form-utility-4k5h.
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
- import * as React from 'react';
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 required = (target: string) => (target.length === 0 ? 'required' : undefined);
36
+ const INPUT_AGE = '9';
37
+ const PARSED_RESULT = Number(INPUT_AGE);
45
38
 
46
- const between = (min: number, max: number) => (target: number) => {
47
- if (target < min) {
48
- return `more than ${min}`;
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
- if (target > max) {
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 INITIAL_FORM_STATE = {
60
- name: '',
61
- age: 13,
62
- seed: 'fish',
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
- const {context, scope} = createFormScope<typeof INITIAL_FORM_STATE>();
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
+ });
@@ -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
+ });
@@ -0,0 +1,2 @@
1
+ import type { ChangeEvent } from 'react';
2
+ export declare function isChangeEvent<T>(x: any): x is ChangeEvent<T>;
package/dist/utils.js ADDED
@@ -0,0 +1,6 @@
1
+ export function isChangeEvent(x) {
2
+ if (typeof x !== 'object') {
3
+ return false;
4
+ }
5
+ return 'target' in x;
6
+ }
package/package.json CHANGED
@@ -1,19 +1,22 @@
1
1
  {
2
2
  "name": "mayoiga",
3
- "version": "0.0.22",
4
- "description": "Form library for React",
5
- "main": "lib/index.js",
6
- "types": "lib/index.d.ts",
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
- "lib"
8
+ "dist"
9
9
  ],
10
10
  "scripts": {
11
- "test": "echo \"Error: no test specified\" && exit 1",
12
- "type-check": "tsc example/src/demo-app.tsx --noEmit --jsx preserve",
13
- "clean": "rm -rf lib/",
14
- "docs": "typedoc --name Mayoiga --mode modules --excludePrivate --out docs/",
15
- "compile": "tsc",
16
- "prepublish": "npm run clean && npm run compile"
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,45 +29,36 @@
26
29
  "validation",
27
30
  "state"
28
31
  ],
29
- "author": "Ogura Daiki",
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.4.4",
37
- "@babel/core": "^7.4.5",
38
- "@babel/plugin-proposal-class-properties": "^7.4.4",
39
- "@babel/plugin-proposal-object-rest-spread": "^7.4.4",
40
- "@babel/preset-env": "^7.4.5",
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.19",
45
- "@types/react-dom": "^16.8.2",
46
- "babel-loader": "^8.0.6",
47
- "prettier": "^1.18.2",
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": "^6.0.0",
53
- "tslint-plugin-prettier": "^2.0.1",
54
- "tslint-react": "^4.0.0",
55
- "typedoc": "^0.14.2",
56
- "typescript": "^3.5.1",
57
- "typescript-eslint-parser": "^22.0.0",
58
- "webpack": "^4.29.6",
59
- "webpack-cli": "^3.3.3",
60
- "webpack-dev-server": "^3.7.1"
61
- },
62
38
  "peerDependencies": {
63
- "react": "^16.8.0"
39
+ "zod": "^3.14.4"
64
40
  },
65
-
66
- "resolutions": {
67
- "@types/react": "^16.8.19"
41
+ "dependencies": {
42
+ "nozuchi": "^0.0.3"
68
43
  },
69
- "dependencies": {}
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
+ }
70
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: readonly 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
@@ -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,82 +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++) {
13
- if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
14
- t[p[i]] = s[p[i]];
15
- }
16
- return t;
17
- };
18
- var __importStar = (this && this.__importStar) || function (mod) {
19
- if (mod && mod.__esModule) return mod;
20
- var result = {};
21
- if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
22
- result["default"] = mod;
23
- return result;
24
- };
25
- Object.defineProperty(exports, "__esModule", { value: true });
26
- const React = __importStar(require("react"));
27
- const react_1 = require("react");
28
- // TODO: mapStateToValue (val: S[Name]) => string
29
- function MappedInputFactory(mapInputToState = val => val /** FIXME: fmmmmmm */) {
30
- return (props) => {
31
- const { name, value, onChange, errors, touched, delegatedProps = {} } = props, restProps = __rest(props, ["name", "value", "onChange", "errors", "touched", "delegatedProps"]);
32
- const handleChange = react_1.useCallback((e) => {
33
- const value = e.currentTarget.value;
34
- onChange(name, mapInputToState(value));
35
- }, [onChange]);
36
- return (React.createElement(React.Fragment, null,
37
- React.createElement("input", Object.assign({}, restProps, delegatedProps, { value: value
38
- ? value /** FIXME: TS 3.5.1 changes defualt infer is unknown */
39
- .toString()
40
- : '', name: name, onChange: handleChange })),
41
- touched && errors.length !== 0 && React.createElement("div", { style: { color: 'red' } }, errors[0])));
42
- };
43
- }
44
- exports.MappedInputFactory = MappedInputFactory;
45
- exports.Input = MappedInputFactory();
46
- exports.NumberInput = MappedInputFactory(val => parseInt(val, 10) || 0);
47
- function MappedRadioFactory(candidates,
48
- /** FIXME: fmmmmmm, I think I can write infer which returns `never` if type is invalid but... */
49
- mapInputToState = val => val) {
50
- return (props) => {
51
- const { name, onChange, errors, touched, delegatedProps = {} } = props, restProps = __rest(props, ["name", "onChange", "errors", "touched", "delegatedProps"]);
52
- const handleChange = react_1.useCallback((e) => {
53
- const value = e.currentTarget.value;
54
- onChange(name, mapInputToState(value));
55
- }, [onChange]);
56
- return (React.createElement(React.Fragment, null,
57
- candidates.map(({ label, value }) => (React.createElement("label", { key: value },
58
- React.createElement("input", Object.assign({}, restProps, delegatedProps, { type: "radio", name: name, value: value, checked: value === props.value, onChange: handleChange })),
59
- label))),
60
- touched && errors.length !== 0 && React.createElement("div", { style: { color: 'red' } }, errors[0])));
61
- };
62
- }
63
- exports.MappedRadioFactory = MappedRadioFactory;
64
- function MappedSelectFactory(options, mapInputToState = val => val) {
65
- return (props) => {
66
- const { value, name, onChange, errors, touched, delegatedProps = {} } = props, restProps = __rest(props, ["value", "name", "onChange", "errors", "touched", "delegatedProps"]);
67
- const handleChange = react_1.useCallback((e) => {
68
- const value = e.currentTarget.value;
69
- onChange(name, mapInputToState(value));
70
- }, [onChange]);
71
- return (React.createElement(React.Fragment, null,
72
- React.createElement("select", Object.assign({}, restProps, delegatedProps, { name: name, value: value
73
- ? value /** TS 3.5.1 changes defualt infer is unknown */
74
- .toString()
75
- : '', onChange: handleChange }),
76
- React.createElement("option", { value: "", disabled: true }, "Please select"),
77
- options.map(({ label, value, disabled }) => (React.createElement("option", { key: value, value: value, disabled: disabled }, label)))),
78
- touched && errors.length !== 0 && React.createElement("div", { style: { color: 'red' } }, errors[0])));
79
- };
80
- }
81
- exports.MappedSelectFactory = MappedSelectFactory;
82
- //# 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":";AAAA;;;;GAIG;;;;;;;;;;;;;;;;;;;;AAEH,6CAA+B;AAE/B,iCAAuE;AAIvE,iDAAiD;AACjD,SAAgB,kBAAkB,CAAC,kBAAwC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,qBAAqB;IACzG,OAAO,CAA0B,KAAwC,EAAE,EAAE;QAC3E,MAAM,EAAC,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,cAAc,GAAG,EAAE,KAAkB,KAAK,EAArB,+FAAqB,CAAC;QAC1F,MAAM,YAAY,GAAG,mBAAW,CAC9B,CAAC,CAAmC,EAAE,EAAE;YACtC,MAAM,KAAK,GAAG,CAAC,CAAC,aAAa,CAAC,KAAK,CAAC;YACpC,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAC,KAAK,CAAC,CAAC,CAAC;QACzC,CAAC,EACD,CAAC,QAAQ,CAAC,CACX,CAAC;QACF,OAAO,CACL;YACE,+CACM,SAAS,EACT,cAAc,IAClB,KAAK,EACH,KAAK;oBACH,CAAC,CAAE,KAAa,CAAC,uDAAuD;yBACnE,QAAQ,EAAE;oBACf,CAAC,CAAC,EAAE,EAER,IAAI,EAAE,IAAc,EACpB,QAAQ,EAAE,YAAY,IACtB;YACD,OAAO,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,IAAI,6BAAK,KAAK,EAAE,EAAC,KAAK,EAAE,KAAK,EAAC,IAAG,MAAM,CAAC,CAAC,CAAC,CAAO,CAC/E,CACJ,CAAC;IACJ,CAAC,CAAC;AACJ,CAAC;AA5BD,gDA4BC;AAEY,QAAA,KAAK,GAAG,kBAAkB,EAAE,CAAC;AAC7B,QAAA,WAAW,GAAG,kBAAkB,CAAC,GAAG,CAAC,EAAE,CAAC,QAAQ,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;AAE7E,SAAgB,kBAAkB,CAChC,UAAsD;AACtD,gGAAgG;AAChG,kBAAwC,GAAG,CAAC,EAAE,CAAC,GAAG;IAElD,OAAO,CAA0B,KAAwC,EAAE,EAAE;QAC3E,MAAM,EAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,cAAc,GAAG,EAAE,KAAkB,KAAK,EAArB,sFAAqB,CAAC;QACnF,MAAM,YAAY,GAAG,mBAAW,CAC9B,CAAC,CAAmC,EAAE,EAAE;YACtC,MAAM,KAAK,GAAG,CAAC,CAAC,aAAa,CAAC,KAAK,CAAC;YACpC,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAC,KAAK,CAAC,CAAC,CAAC;QACzC,CAAC,EACD,CAAC,QAAQ,CAAC,CACX,CAAC;QACF,OAAO,CACL;YACG,UAAU,CAAC,GAAG,CAAC,CAAC,EAAC,KAAK,EAAE,KAAK,EAAC,EAAE,EAAE,CAAC,CAClC,+BAAO,GAAG,EAAE,KAAK;gBACf,+CACM,SAAS,EACT,cAAc,IAClB,IAAI,EAAC,OAAO,EACZ,IAAI,EAAE,IAAc,EACpB,KAAK,EAAE,KAAK,EACZ,OAAO,EAAE,KAAK,KAAK,KAAK,CAAC,KAAK,EAC9B,QAAQ,EAAE,YAAY,IACtB;gBACD,KAAK,CACA,CACT,CAAC;YACD,OAAO,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,IAAI,6BAAK,KAAK,EAAE,EAAC,KAAK,EAAE,KAAK,EAAC,IAAG,MAAM,CAAC,CAAC,CAAC,CAAO,CAC/E,CACJ,CAAC;IACJ,CAAC,CAAC;AACJ,CAAC;AAlCD,gDAkCC;AAED,SAAgB,mBAAmB,CACjC,OAAuE,EACvE,kBAAwC,GAAG,CAAC,EAAE,CAAC,GAAG;IAElD,OAAO,CAA0B,KAAwC,EAAE,EAAE;QAC3E,MAAM,EAAC,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,cAAc,GAAG,EAAE,KAAkB,KAAK,EAArB,+FAAqB,CAAC;QAC1F,MAAM,YAAY,GAAG,mBAAW,CAC9B,CAAC,CAAoC,EAAE,EAAE;YACvC,MAAM,KAAK,GAAG,CAAC,CAAC,aAAa,CAAC,KAAK,CAAC;YACpC,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAC,KAAK,CAAC,CAAC,CAAC;QACzC,CAAC,EACD,CAAC,QAAQ,CAAC,CACX,CAAC;QACF,OAAO,CACL;YACE,gDACM,SAAS,EACT,cAAc,IAClB,IAAI,EAAE,IAAc,EACpB,KAAK,EACH,KAAK;oBACH,CAAC,CAAE,KAAa,CAAC,gDAAgD;yBAC5D,QAAQ,EAAE;oBACf,CAAC,CAAC,EAAE,EAER,QAAQ,EAAE,YAAY;gBAEtB,gCAAQ,KAAK,EAAC,EAAE,EAAC,QAAQ,EAAE,IAAI,oBAEtB;gBACR,OAAO,CAAC,GAAG,CAAC,CAAC,EAAC,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAC,EAAE,EAAE,CAAC,CACzC,gCAAQ,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,IACjD,KAAK,CACC,CACV,CAAC,CACK;YACR,OAAO,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,IAAI,6BAAK,KAAK,EAAE,EAAC,KAAK,EAAE,KAAK,EAAC,IAAG,MAAM,CAAC,CAAC,CAAC,CAAO,CAC/E,CACJ,CAAC;IACJ,CAAC,CAAC;AACJ,CAAC;AAxCD,kDAwCC"}
package/lib/index.d.ts DELETED
@@ -1,3 +0,0 @@
1
- import { useForm, useFormState, createFormScope } from './mayoiga';
2
- import { InputProtocol } from './inputProtocol';
3
- export { useForm, useFormState, createFormScope, InputProtocol };
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"}
@@ -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 {};
@@ -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
@@ -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,110 +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
- className?: string;
20
- onSubmit?(value: S): void;
21
- children: ReactNode;
22
- };
23
- declare type FieldProps<S, Name extends keyof S, ComponentProps> = {
24
- /** One on the name of field. Have to be `keyof State`. */
25
- name: Name;
26
- /** You might want to handle onChange event in case. */
27
- onChange?(name: Name, value: S[Name]): void;
28
- validations?: ReadonlyArray<Validator<S, Name>>;
29
- /**
30
- * Props to delegate to the input component.
31
- * @example
32
- * ```
33
- *
34
- * componentProps={{disabled: props.formState.age >= 20}}
35
- * component={AlcoholCheckbox}
36
- * ```
37
- */
38
- componentProps?: ComponentProps;
39
- /** Component which can handle the field of the form state. It should implement {@link InputProtocol}. */
40
- component: FC<InputProtocol<S, Name, ComponentProps>>;
41
- };
42
- declare type MayoigaProps<S> = {
43
- initialState: S;
44
- onSubmit(hasErrors: boolean, value: S, formInfo: {
45
- errors: Store<S>['errors'];
46
- touched: boolean;
47
- }): void;
48
- };
49
- /** @obsolete */
50
- declare type ScopedComponentProps<S> = {
51
- touched: boolean;
52
- errors: Store<S>['errors'];
53
- formState: S;
54
- };
55
- /**
56
- * A function to create scope which allows to use `useForm` function to manage form value.
57
- *
58
- * @example
59
- * ```
60
- *
61
- * type FormState = {name: '', age: 0};
62
- * const {context, scope} = createFormScope<FormState>();
63
- *
64
- * const TheForm = scope(props => {
65
- * const {Form, Field} = useForm(context);
66
- * // ...
67
- * ```
68
- *
69
- */
70
- export declare function createFormScope<S>(): {
71
- context: React.Context<MayoigaContextValue<S>>;
72
- scope: <OwnProps = {}>(ConnectedComponent: React.FunctionComponent<OwnProps & ScopedComponentProps<S>>) => (props: OwnProps & MayoigaProps<S>) => JSX.Element;
73
- };
74
- /**
75
- * The Hook to summon type safe React form component.
76
- * To know the props that Field can accept {@link FieldProps}.
77
- *
78
- * @example
79
- * ```
80
- *
81
- * const TheForm = scope(props => {
82
- * const {Form, Field} = useForm(context);
83
- * return (
84
- * <Form>
85
- * <Field name="name" component={props => <input value={props.value.toString()} />} validations={[required]} />
86
- * <Field name="fieldNameHaven'tDefined" <= you will see compile error if you were specified the name haven't declared in the context type for initialState.
87
- * </Form>
88
- * );
89
- * ```
90
- *
91
- * You should be aware, `component` props for <Field /> is going to be cached at the first render. So it should be same pure function.
92
- */
93
- export declare function useForm<S>(formScope: Context<MayoigaContextValue<S>>): {
94
- Form: React.MemoExoticComponent<(props: FormProps<S>) => JSX.Element>;
95
- Field: <Name extends Extract<keyof S, string>, ComponentProps = undefined>(props: FieldProps<S, Name, ComponentProps>) => JSX.Element;
96
- useFormState(): [Store<S>, (newState: Partial<S>) => void];
97
- };
98
- /**
99
- * You can get current form state and also change it:
100
- *
101
- * ```
102
- * const [{ errors, formData }, setFormState] = useFormState(context);
103
- * ...
104
- * onChange={(name, value) => {
105
- * if (value) setFormState({otherFieldValue: ''});
106
- * }}
107
- * ```
108
- */
109
- export declare function useFormState<S>(formScope: Context<MayoigaContextValue<S>>): [Store<S>, (newState: Partial<S>) => void];
110
- export {};
package/lib/mayoiga.js DELETED
@@ -1,131 +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={props => <input value={props.value.toString()} />} 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
- * You should be aware, `component` props for <Field /> is going to be cached at the first render. So it should be same pure function.
67
- */
68
- function useForm(formScope) {
69
- return react_1.useMemo(() => ({
70
- Form: react_1.memo((props) => {
71
- const { state, dispatch } = react_1.useContext(formScope);
72
- const { className, onSubmit } = props;
73
- return (React.createElement("form", { className: className, onSubmit: e => {
74
- e.preventDefault();
75
- if (onSubmit) {
76
- onSubmit(state.formData);
77
- }
78
- dispatch(actions_1.submitValue(state.formData));
79
- } }, props.children));
80
- }),
81
- Field: (props) => {
82
- const { state, dispatch } = react_1.useContext(formScope);
83
- const { component, name, validations, componentProps, onChange } = props;
84
- const validate = (target) => {
85
- if (validations !== undefined) {
86
- const errors = validations.map(v => v(target, state.formData)).filter((result) => !!result);
87
- dispatch(actions_1.sendErrors(name, errors.length === 0 ? [] : errors));
88
- }
89
- };
90
- const handleChange = react_1.useCallback((name, value) => {
91
- dispatch(actions_1.changeField(name, value));
92
- if (onChange) {
93
- onChange(name, value);
94
- }
95
- validate(value);
96
- }, [name, state, componentProps]);
97
- const value = state.formData[name];
98
- const errors = state.errors[name];
99
- const touched = state.touched[name];
100
- react_1.useEffect(() => {
101
- // I'm not exactly sure why but we need it to accomplish dispatch action on mounted. :(
102
- window.requestAnimationFrame(() => validate(value));
103
- }, [state.formData]);
104
- // cache component for performance
105
- const Component = react_1.useMemo(() => component, [name]);
106
- return (React.createElement(Component, { name: name, value: value /** FIXME: */, onChange: handleChange, errors: errors, touched: touched, delegatedProps: componentProps }));
107
- },
108
- useFormState() {
109
- const { state, dispatch } = react_1.useContext(formScope);
110
- return [state, (newState) => dispatch(actions_1.setNewState(newState))];
111
- },
112
- }), [formScope]);
113
- }
114
- exports.useForm = useForm;
115
- /**
116
- * You can get current form state and also change it:
117
- *
118
- * ```
119
- * const [{ errors, formData }, setFormState] = useFormState(context);
120
- * ...
121
- * onChange={(name, value) => {
122
- * if (value) setFormState({otherFieldValue: ''});
123
- * }}
124
- * ```
125
- */
126
- function useFormState(formScope) {
127
- const { state, dispatch } = react_1.useContext(formScope);
128
- return [state, (newState) => dispatch(actions_1.setNewState(newState))];
129
- }
130
- exports.useFormState = useFormState;
131
- //# sourceMappingURL=mayoiga.js.map
@@ -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
@@ -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"}