oxform-react 0.1.0 → 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/dist/index.cjs +179 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +76 -0
- package/dist/index.d.ts +76 -0
- package/dist/index.js +171 -0
- package/dist/index.js.map +1 -0
- package/package.json +20 -14
- package/export/index.ts +0 -3
- package/src/testing/performance.tsx +0 -59
- package/src/tests/use-field.spec.tsx +0 -116
- package/src/use-field.ts +0 -81
- package/src/use-form-status.ts +0 -28
- package/src/use-form.ts +0 -20
- package/src/use-isomorphic-layout-effect.ts +0 -3
- package/tsconfig.json +0 -6
- package/tsdown.config.ts +0 -9
- package/vitest.config.ts +0 -10
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
let oxform_core = require("oxform-core");
|
|
2
|
+
let react = require("react");
|
|
3
|
+
let _tanstack_react_store = require("@tanstack/react-store");
|
|
4
|
+
|
|
5
|
+
//#region src/use-isomorphic-layout-effect.ts
|
|
6
|
+
const useIsomorphicLayoutEffect = typeof window !== "undefined" ? react.useLayoutEffect : react.useEffect;
|
|
7
|
+
|
|
8
|
+
//#endregion
|
|
9
|
+
//#region src/use-subscribe.ts
|
|
10
|
+
const useSubscribe = (api, selector) => {
|
|
11
|
+
return (0, _tanstack_react_store.useStore)(api.store(), selector);
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
//#endregion
|
|
15
|
+
//#region src/use-array-field.ts
|
|
16
|
+
const useArrayField = (options) => {
|
|
17
|
+
const [api] = (0, react.useState)(() => {
|
|
18
|
+
return (0, oxform_core.createArrayFieldApi)({ ...options });
|
|
19
|
+
});
|
|
20
|
+
useIsomorphicLayoutEffect(api["~mount"], [api]);
|
|
21
|
+
useIsomorphicLayoutEffect(() => {
|
|
22
|
+
api["~update"](options);
|
|
23
|
+
});
|
|
24
|
+
return (0, react.useMemo)(() => {
|
|
25
|
+
return {
|
|
26
|
+
...api,
|
|
27
|
+
get fields() {
|
|
28
|
+
return api.state().value;
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
}, [api, useSubscribe(api, (state) => Object.keys(state.value ?? []).length)]);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
//#endregion
|
|
35
|
+
//#region src/use-field-api.ts
|
|
36
|
+
const useFieldApi = (options) => {
|
|
37
|
+
const [api] = (0, react.useState)(() => {
|
|
38
|
+
return (0, oxform_core.createFieldApi)(options);
|
|
39
|
+
});
|
|
40
|
+
useIsomorphicLayoutEffect(api["~mount"], [api]);
|
|
41
|
+
useIsomorphicLayoutEffect(() => {
|
|
42
|
+
api["~update"](options);
|
|
43
|
+
});
|
|
44
|
+
return api;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
//#endregion
|
|
48
|
+
//#region src/use-field.ts
|
|
49
|
+
const useField = (options) => {
|
|
50
|
+
const api = useFieldApi(options);
|
|
51
|
+
const value = (0, _tanstack_react_store.useStore)(api.store(), (state) => state.value);
|
|
52
|
+
const defaultValue = (0, _tanstack_react_store.useStore)(api.store(), (state) => state.defaultValue);
|
|
53
|
+
const dirty = (0, _tanstack_react_store.useStore)(api.store(), (state) => state.meta.dirty);
|
|
54
|
+
const touched = (0, _tanstack_react_store.useStore)(api.store(), (state) => state.meta.touched);
|
|
55
|
+
const blurred = (0, _tanstack_react_store.useStore)(api.store(), (state) => state.meta.blurred);
|
|
56
|
+
const pristine = (0, _tanstack_react_store.useStore)(api.store(), (state) => state.meta.pristine);
|
|
57
|
+
const valid = (0, _tanstack_react_store.useStore)(api.store(), (state) => state.meta.valid);
|
|
58
|
+
const isDefault = (0, _tanstack_react_store.useStore)(api.store(), (state) => state.meta.default);
|
|
59
|
+
const errors = (0, _tanstack_react_store.useStore)(api.store(), (state) => state.errors);
|
|
60
|
+
return (0, react.useMemo)(() => {
|
|
61
|
+
return {
|
|
62
|
+
...api,
|
|
63
|
+
state: {
|
|
64
|
+
value,
|
|
65
|
+
defaultValue,
|
|
66
|
+
errors,
|
|
67
|
+
meta: {
|
|
68
|
+
blurred,
|
|
69
|
+
default: isDefault,
|
|
70
|
+
dirty,
|
|
71
|
+
pristine,
|
|
72
|
+
touched,
|
|
73
|
+
valid
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
props: {
|
|
77
|
+
value,
|
|
78
|
+
onBlur: api.blur,
|
|
79
|
+
onFocus: api.focus,
|
|
80
|
+
onChange: (event) => api.change(event.target?.value),
|
|
81
|
+
ref: api.register
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
}, [
|
|
85
|
+
api,
|
|
86
|
+
errors,
|
|
87
|
+
value,
|
|
88
|
+
defaultValue,
|
|
89
|
+
dirty,
|
|
90
|
+
touched,
|
|
91
|
+
blurred,
|
|
92
|
+
pristine,
|
|
93
|
+
valid,
|
|
94
|
+
isDefault
|
|
95
|
+
]);
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
//#endregion
|
|
99
|
+
//#region src/use-form.ts
|
|
100
|
+
const useForm = (options) => {
|
|
101
|
+
const [api] = (0, react.useState)(() => {
|
|
102
|
+
return new oxform_core.FormApi({ ...options });
|
|
103
|
+
});
|
|
104
|
+
useIsomorphicLayoutEffect(api["~mount"], [api]);
|
|
105
|
+
useIsomorphicLayoutEffect(() => {
|
|
106
|
+
api["~update"](options);
|
|
107
|
+
});
|
|
108
|
+
return api;
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
//#endregion
|
|
112
|
+
//#region src/use-form-status.ts
|
|
113
|
+
const useFormStatus = ({ form }) => {
|
|
114
|
+
const dirty = (0, _tanstack_react_store.useStore)(form.store(), (state) => state.status.dirty);
|
|
115
|
+
const valid = (0, _tanstack_react_store.useStore)(form.store(), (state) => state.status.valid);
|
|
116
|
+
const submitting = (0, _tanstack_react_store.useStore)(form.store(), (state) => state.status.submitting);
|
|
117
|
+
const successful = (0, _tanstack_react_store.useStore)(form.store(), (state) => state.status.successful);
|
|
118
|
+
const validating = (0, _tanstack_react_store.useStore)(form.store(), (state) => state.status.validating);
|
|
119
|
+
const submits = (0, _tanstack_react_store.useStore)(form.store(), (state) => state.status.submits);
|
|
120
|
+
const submitted = (0, _tanstack_react_store.useStore)(form.store(), (state) => state.status.submitted);
|
|
121
|
+
return (0, react.useMemo)(() => {
|
|
122
|
+
return {
|
|
123
|
+
dirty,
|
|
124
|
+
valid,
|
|
125
|
+
submitting,
|
|
126
|
+
successful,
|
|
127
|
+
validating,
|
|
128
|
+
submits,
|
|
129
|
+
submitted
|
|
130
|
+
};
|
|
131
|
+
}, [
|
|
132
|
+
dirty,
|
|
133
|
+
valid,
|
|
134
|
+
submitting,
|
|
135
|
+
successful,
|
|
136
|
+
validating,
|
|
137
|
+
submits,
|
|
138
|
+
submitted
|
|
139
|
+
]);
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
//#endregion
|
|
143
|
+
//#region src/array-field.tsx
|
|
144
|
+
const ArrayField = ({ children, ...options }) => {
|
|
145
|
+
const field = useArrayField(options);
|
|
146
|
+
return (0, react.useMemo)(() => {
|
|
147
|
+
return typeof children === "function" ? children(field) : children;
|
|
148
|
+
}, [children, field]);
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
//#endregion
|
|
152
|
+
//#region src/field.tsx
|
|
153
|
+
const Field = ({ children, ...options }) => {
|
|
154
|
+
const field = useField(options);
|
|
155
|
+
return (0, react.useMemo)(() => {
|
|
156
|
+
return typeof children === "function" ? children(field) : children;
|
|
157
|
+
}, [children, field]);
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
//#endregion
|
|
161
|
+
//#region src/subscribe.tsx
|
|
162
|
+
const Subscribe = ({ api, selector, children }) => {
|
|
163
|
+
const selected = useSubscribe(api, selector);
|
|
164
|
+
return (0, react.useMemo)(() => {
|
|
165
|
+
return typeof children === "function" ? children(selected) : children;
|
|
166
|
+
}, [children, selected]);
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
//#endregion
|
|
170
|
+
exports.ArrayField = ArrayField;
|
|
171
|
+
exports.Field = Field;
|
|
172
|
+
exports.Subscribe = Subscribe;
|
|
173
|
+
exports.useArrayField = useArrayField;
|
|
174
|
+
exports.useField = useField;
|
|
175
|
+
exports.useFieldApi = useFieldApi;
|
|
176
|
+
exports.useForm = useForm;
|
|
177
|
+
exports.useFormStatus = useFormStatus;
|
|
178
|
+
exports.useSubscribe = useSubscribe;
|
|
179
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["useLayoutEffect","useEffect","FormApi"],"sources":["../src/use-isomorphic-layout-effect.ts","../src/use-subscribe.ts","../src/use-array-field.ts","../src/use-field-api.ts","../src/use-field.ts","../src/use-form.ts","../src/use-form-status.ts","../src/array-field.tsx","../src/field.tsx","../src/subscribe.tsx"],"sourcesContent":["import { useEffect, useLayoutEffect } from 'react';\n\nexport const useIsomorphicLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect;\n","import { useStore } from '@tanstack/react-store';\nimport { type AnyFormLikeApi, type ApiSelector } from 'oxform-core';\n\nexport const useSubscribe = <Api extends AnyFormLikeApi, Selected>(api: Api, selector: ApiSelector<Api, Selected>) => {\n return useStore(api.store() as never, selector as never) as Selected;\n};\n\n// const schema = z.object({ name: z.string(), names: z.string().array() });\n// const form = new FormApi({ schema, defaultValues: { name: 'John', names: ['Jane', 'Bob'] } });\n// const field = new FieldApi({ form, name: 'name' });\n// const array = new ArrayFieldApi({ form, name: 'names' });\n\n// const a = useSubscribe(form, state => state.values);\n// const b = useSubscribe(field, state => state.value);\n// const c = useSubscribe(array, state => state.value);\n","import { ArrayFieldApi, createArrayFieldApi } from 'oxform-core';\n\nimport { useIsomorphicLayoutEffect } from '#use-isomorphic-layout-effect';\nimport { useSubscribe } from '#use-subscribe';\nimport type { AnyFormApi, ArrayFieldOptions, ArrayLike, FormArrayFields, FormFieldValue } from 'oxform-core';\nimport { useMemo, useState } from 'react';\n\nexport type UseArrayFieldReturn<Value extends ArrayLike> = Omit<ArrayFieldApi<Value>, '~mount' | '~update'> & {\n fields: Value;\n};\n\nexport const useArrayField = <Form extends AnyFormApi, const Name extends FormArrayFields<Form>>(\n options: ArrayFieldOptions<Form, Name>,\n): UseArrayFieldReturn<FormFieldValue<Form, Name>> => {\n const [api] = useState(() => {\n return createArrayFieldApi({ ...options });\n });\n\n useIsomorphicLayoutEffect(api['~mount'], [api]);\n useIsomorphicLayoutEffect(() => {\n api['~update'](options);\n });\n\n // todo: re-create api if form or name changes\n // spike: use optional context to cache the api instance\n\n const length = useSubscribe(api, state => Object.keys((state.value as unknown) ?? []).length);\n\n return useMemo(() => {\n void length;\n\n return {\n ...api,\n get fields() {\n return api.state().value;\n },\n } satisfies UseArrayFieldReturn<FormFieldValue<Form, Name>>;\n }, [api, length]);\n};\n","import { createFieldApi, FieldApi, type FieldOptions } from 'oxform-core';\n\nimport { useIsomorphicLayoutEffect } from '#use-isomorphic-layout-effect';\nimport type { AnyFormApi, FormFields, FormFieldValue } from 'oxform-core';\nimport { useState } from 'react';\n\nexport type UseFieldApiReturn<Value> = FieldApi<Value>;\n\nexport const useFieldApi = <Form extends AnyFormApi, const Name extends FormFields<Form>>(\n options: FieldOptions<Form, Name>,\n): UseFieldApiReturn<FormFieldValue<Form, Name>> => {\n const [api] = useState(() => {\n return createFieldApi(options);\n });\n\n // todo: re-create api if form or name changes\n // spike: use optional context to cache the api instance\n\n useIsomorphicLayoutEffect(api['~mount'], [api]);\n useIsomorphicLayoutEffect(() => {\n api['~update'](options);\n });\n\n return api;\n};\n","import type { AnyFormApi, EventLike, FieldState, FormFields, FormFieldValue } from 'oxform-core';\nimport { FieldApi, type FieldOptions } from 'oxform-core';\n\nimport { useFieldApi } from '#use-field-api';\nimport { useStore } from '@tanstack/react-store';\nimport { useMemo } from 'react';\n\nexport type UseFieldReturn<Value> = Omit<FieldApi<Value>, '~mount' | '~update' | 'state'> & {\n state: FieldState<Value>;\n props: {\n value: Value;\n ref: (element: HTMLElement | null) => void;\n onChange: (event: EventLike) => void;\n onBlur: (event: EventLike) => void;\n onFocus: (event: EventLike) => void;\n };\n};\n\nexport const useField = <Form extends AnyFormApi, const Name extends FormFields<Form>>(\n options: FieldOptions<Form, Name>,\n): UseFieldReturn<FormFieldValue<Form, Name>> => {\n const api = useFieldApi(options);\n\n const value = useStore(api.store(), state => state.value);\n const defaultValue = useStore(api.store(), state => state.defaultValue);\n const dirty = useStore(api.store(), state => state.meta.dirty);\n const touched = useStore(api.store(), state => state.meta.touched);\n const blurred = useStore(api.store(), state => state.meta.blurred);\n const pristine = useStore(api.store(), state => state.meta.pristine);\n const valid = useStore(api.store(), state => state.meta.valid);\n const isDefault = useStore(api.store(), state => state.meta.default);\n const errors = useStore(api.store(), state => state.errors);\n\n return useMemo(() => {\n return {\n ...api,\n\n state: {\n value,\n defaultValue,\n errors,\n meta: {\n blurred,\n default: isDefault,\n dirty,\n pristine,\n touched,\n valid,\n },\n },\n\n props: {\n value,\n onBlur: api.blur,\n onFocus: api.focus,\n onChange: (event: EventLike) => api.change(event.target?.value),\n ref: api.register,\n },\n } satisfies UseFieldReturn<any> as any;\n }, [api, errors, value, defaultValue, dirty, touched, blurred, pristine, valid, isDefault]);\n};\n","import { useIsomorphicLayoutEffect } from '#use-isomorphic-layout-effect';\nimport type { FormOptions } from 'oxform-core';\nimport { FormApi } from 'oxform-core';\nimport { useState } from 'react';\n\nexport type UseFormReturn<Values> = FormApi<Values>;\n\nexport const useForm = <Values>(options: FormOptions<Values>) => {\n const [api] = useState(() => {\n return new FormApi({ ...options });\n });\n\n useIsomorphicLayoutEffect(api['~mount'], [api]);\n useIsomorphicLayoutEffect(() => {\n api['~update'](options);\n });\n\n // todo: re-create api if id changes\n\n return api satisfies UseFormReturn<Values> as UseFormReturn<Values>;\n};\n","import { useStore } from '@tanstack/react-store';\nimport type { FormApi, FormStatus } from 'oxform-core';\nimport { useMemo } from 'react';\n\nexport type UseFormStatusProps<Values> = {\n form: FormApi<Values>;\n};\n\nexport type UseFormStatusReturn = FormStatus;\n\nexport const useFormStatus = <Values>({ form }: UseFormStatusProps<Values>): UseFormStatusReturn => {\n const dirty = useStore(form.store(), state => state.status.dirty);\n const valid = useStore(form.store(), state => state.status.valid);\n const submitting = useStore(form.store(), state => state.status.submitting);\n const successful = useStore(form.store(), state => state.status.successful);\n const validating = useStore(form.store(), state => state.status.validating);\n const submits = useStore(form.store(), state => state.status.submits);\n const submitted = useStore(form.store(), state => state.status.submitted);\n\n return useMemo(() => {\n return {\n dirty,\n valid,\n submitting,\n successful,\n validating,\n submits,\n submitted,\n } satisfies UseFormStatusReturn;\n }, [dirty, valid, submitting, successful, validating, submits, submitted]);\n};\n","import { useArrayField, type UseArrayFieldReturn } from '#use-array-field';\nimport type { AnyFormApi, FieldOptions, FormArrayFields, FormFieldValue } from 'oxform-core';\nimport { useMemo } from 'react';\n\nexport type ArrayFieldProps<Form extends AnyFormApi, Name extends FormArrayFields<Form>> = FieldOptions<Form, Name> & {\n children: React.ReactNode | ((field: UseArrayFieldReturn<FormFieldValue<Form, Name>>) => React.ReactNode);\n};\n\nexport const ArrayField = <Form extends AnyFormApi, const Name extends FormArrayFields<Form>>({\n children,\n ...options\n}: ArrayFieldProps<Form, Name>) => {\n const field = useArrayField(options);\n\n return useMemo(() => {\n return typeof children === 'function' ? children(field) : children;\n }, [children, field]);\n};\n","import { useField, type UseFieldReturn } from '#use-field';\nimport type { AnyFormApi, FieldOptions, FormFields, FormFieldValue } from 'oxform-core';\nimport { useMemo } from 'react';\n\nexport type FieldProps<Form extends AnyFormApi, Name extends FormFields<Form>> = FieldOptions<Form, Name> & {\n children: React.ReactNode | ((field: UseFieldReturn<FormFieldValue<Form, Name>>) => React.ReactNode);\n};\n\nexport const Field = <Form extends AnyFormApi, const Name extends FormFields<Form>>({\n children,\n ...options\n}: FieldProps<Form, Name>) => {\n const field = useField(options);\n\n return useMemo(() => {\n return typeof children === 'function' ? children(field) : children;\n }, [children, field]);\n};\n","import { useSubscribe } from '#use-subscribe';\nimport type { AnyFormLikeApi, ApiSelector } from 'oxform-core';\nimport { useMemo } from 'react';\n\nexport type SubscribeProps<Api extends AnyFormLikeApi, Selected> = {\n api: Api;\n selector: ApiSelector<Api, Selected>;\n} & {\n children: React.ReactNode | ((field: Selected) => React.ReactNode);\n};\n\nexport const Subscribe = <Api extends AnyFormLikeApi, Selected>({\n api,\n selector,\n children,\n}: SubscribeProps<Api, Selected>) => {\n const selected = useSubscribe(api, selector);\n\n return useMemo(() => {\n return typeof children === 'function' ? children(selected as never) : children;\n }, [children, selected]);\n};\n"],"mappings":";;;;;AAEA,MAAa,4BAA4B,OAAO,WAAW,cAAcA,wBAAkBC;;;;ACC3F,MAAa,gBAAsD,KAAU,aAAyC;AACpH,4CAAgB,IAAI,OAAO,EAAW,SAAkB;;;;;ACO1D,MAAa,iBACX,YACoD;CACpD,MAAM,CAAC,iCAAsB;AAC3B,8CAA2B,EAAE,GAAG,SAAS,CAAC;GAC1C;AAEF,2BAA0B,IAAI,WAAW,CAAC,IAAI,CAAC;AAC/C,iCAAgC;AAC9B,MAAI,WAAW,QAAQ;GACvB;AAOF,iCAAqB;AAGnB,SAAO;GACL,GAAG;GACH,IAAI,SAAS;AACX,WAAO,IAAI,OAAO,CAAC;;GAEtB;IACA,CAAC,KAXW,aAAa,MAAK,UAAS,OAAO,KAAM,MAAM,SAAqB,EAAE,CAAC,CAAC,OAAO,CAW7E,CAAC;;;;;AC7BnB,MAAa,eACX,YACkD;CAClD,MAAM,CAAC,iCAAsB;AAC3B,yCAAsB,QAAQ;GAC9B;AAKF,2BAA0B,IAAI,WAAW,CAAC,IAAI,CAAC;AAC/C,iCAAgC;AAC9B,MAAI,WAAW,QAAQ;GACvB;AAEF,QAAO;;;;;ACLT,MAAa,YACX,YAC+C;CAC/C,MAAM,MAAM,YAAY,QAAQ;CAEhC,MAAM,4CAAiB,IAAI,OAAO,GAAE,UAAS,MAAM,MAAM;CACzD,MAAM,mDAAwB,IAAI,OAAO,GAAE,UAAS,MAAM,aAAa;CACvE,MAAM,4CAAiB,IAAI,OAAO,GAAE,UAAS,MAAM,KAAK,MAAM;CAC9D,MAAM,8CAAmB,IAAI,OAAO,GAAE,UAAS,MAAM,KAAK,QAAQ;CAClE,MAAM,8CAAmB,IAAI,OAAO,GAAE,UAAS,MAAM,KAAK,QAAQ;CAClE,MAAM,+CAAoB,IAAI,OAAO,GAAE,UAAS,MAAM,KAAK,SAAS;CACpE,MAAM,4CAAiB,IAAI,OAAO,GAAE,UAAS,MAAM,KAAK,MAAM;CAC9D,MAAM,gDAAqB,IAAI,OAAO,GAAE,UAAS,MAAM,KAAK,QAAQ;CACpE,MAAM,6CAAkB,IAAI,OAAO,GAAE,UAAS,MAAM,OAAO;AAE3D,iCAAqB;AACnB,SAAO;GACL,GAAG;GAEH,OAAO;IACL;IACA;IACA;IACA,MAAM;KACJ;KACA,SAAS;KACT;KACA;KACA;KACA;KACD;IACF;GAED,OAAO;IACL;IACA,QAAQ,IAAI;IACZ,SAAS,IAAI;IACb,WAAW,UAAqB,IAAI,OAAO,MAAM,QAAQ,MAAM;IAC/D,KAAK,IAAI;IACV;GACF;IACA;EAAC;EAAK;EAAQ;EAAO;EAAc;EAAO;EAAS;EAAS;EAAU;EAAO;EAAU,CAAC;;;;;ACpD7F,MAAa,WAAmB,YAAiC;CAC/D,MAAM,CAAC,iCAAsB;AAC3B,SAAO,IAAIC,oBAAQ,EAAE,GAAG,SAAS,CAAC;GAClC;AAEF,2BAA0B,IAAI,WAAW,CAAC,IAAI,CAAC;AAC/C,iCAAgC;AAC9B,MAAI,WAAW,QAAQ;GACvB;AAIF,QAAO;;;;;ACTT,MAAa,iBAAyB,EAAE,WAA4D;CAClG,MAAM,4CAAiB,KAAK,OAAO,GAAE,UAAS,MAAM,OAAO,MAAM;CACjE,MAAM,4CAAiB,KAAK,OAAO,GAAE,UAAS,MAAM,OAAO,MAAM;CACjE,MAAM,iDAAsB,KAAK,OAAO,GAAE,UAAS,MAAM,OAAO,WAAW;CAC3E,MAAM,iDAAsB,KAAK,OAAO,GAAE,UAAS,MAAM,OAAO,WAAW;CAC3E,MAAM,iDAAsB,KAAK,OAAO,GAAE,UAAS,MAAM,OAAO,WAAW;CAC3E,MAAM,8CAAmB,KAAK,OAAO,GAAE,UAAS,MAAM,OAAO,QAAQ;CACrE,MAAM,gDAAqB,KAAK,OAAO,GAAE,UAAS,MAAM,OAAO,UAAU;AAEzE,iCAAqB;AACnB,SAAO;GACL;GACA;GACA;GACA;GACA;GACA;GACA;GACD;IACA;EAAC;EAAO;EAAO;EAAY;EAAY;EAAY;EAAS;EAAU,CAAC;;;;;ACrB5E,MAAa,cAAiF,EAC5F,UACA,GAAG,cAC8B;CACjC,MAAM,QAAQ,cAAc,QAAQ;AAEpC,iCAAqB;AACnB,SAAO,OAAO,aAAa,aAAa,SAAS,MAAM,GAAG;IACzD,CAAC,UAAU,MAAM,CAAC;;;;;ACRvB,MAAa,SAAuE,EAClF,UACA,GAAG,cACyB;CAC5B,MAAM,QAAQ,SAAS,QAAQ;AAE/B,iCAAqB;AACnB,SAAO,OAAO,aAAa,aAAa,SAAS,MAAM,GAAG;IACzD,CAAC,UAAU,MAAM,CAAC;;;;;ACLvB,MAAa,aAAmD,EAC9D,KACA,UACA,eACmC;CACnC,MAAM,WAAW,aAAa,KAAK,SAAS;AAE5C,iCAAqB;AACnB,SAAO,OAAO,aAAa,aAAa,SAAS,SAAkB,GAAG;IACrE,CAAC,UAAU,SAAS,CAAC"}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { AnyFormApi, AnyFormLikeApi, ApiSelector, ArrayFieldApi, ArrayFieldOptions, ArrayLike, EventLike, FieldApi, FieldOptions, FieldState, FormApi, FormArrayFields, FormFieldValue, FormFields, FormOptions, FormStatus } from "oxform-core";
|
|
2
|
+
import * as react0 from "react";
|
|
3
|
+
export * from "oxform-core";
|
|
4
|
+
|
|
5
|
+
//#region src/use-array-field.d.ts
|
|
6
|
+
type UseArrayFieldReturn<Value extends ArrayLike> = Omit<ArrayFieldApi<Value>, '~mount' | '~update'> & {
|
|
7
|
+
fields: Value;
|
|
8
|
+
};
|
|
9
|
+
declare const useArrayField: <Form extends AnyFormApi, const Name extends FormArrayFields<Form>>(options: ArrayFieldOptions<Form, Name>) => UseArrayFieldReturn<FormFieldValue<Form, Name>>;
|
|
10
|
+
//#endregion
|
|
11
|
+
//#region src/use-field.d.ts
|
|
12
|
+
type UseFieldReturn<Value> = Omit<FieldApi<Value>, '~mount' | '~update' | 'state'> & {
|
|
13
|
+
state: FieldState<Value>;
|
|
14
|
+
props: {
|
|
15
|
+
value: Value;
|
|
16
|
+
ref: (element: HTMLElement | null) => void;
|
|
17
|
+
onChange: (event: EventLike) => void;
|
|
18
|
+
onBlur: (event: EventLike) => void;
|
|
19
|
+
onFocus: (event: EventLike) => void;
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
declare const useField: <Form extends AnyFormApi, const Name extends FormFields<Form>>(options: FieldOptions<Form, Name>) => UseFieldReturn<FormFieldValue<Form, Name>>;
|
|
23
|
+
//#endregion
|
|
24
|
+
//#region src/use-field-api.d.ts
|
|
25
|
+
type UseFieldApiReturn<Value> = FieldApi<Value>;
|
|
26
|
+
declare const useFieldApi: <Form extends AnyFormApi, const Name extends FormFields<Form>>(options: FieldOptions<Form, Name>) => UseFieldApiReturn<FormFieldValue<Form, Name>>;
|
|
27
|
+
//#endregion
|
|
28
|
+
//#region src/use-form.d.ts
|
|
29
|
+
type UseFormReturn<Values> = FormApi<Values>;
|
|
30
|
+
declare const useForm: <Values>(options: FormOptions<Values>) => UseFormReturn<Values>;
|
|
31
|
+
//#endregion
|
|
32
|
+
//#region src/use-form-status.d.ts
|
|
33
|
+
type UseFormStatusProps<Values> = {
|
|
34
|
+
form: FormApi<Values>;
|
|
35
|
+
};
|
|
36
|
+
type UseFormStatusReturn = FormStatus;
|
|
37
|
+
declare const useFormStatus: <Values>({
|
|
38
|
+
form
|
|
39
|
+
}: UseFormStatusProps<Values>) => UseFormStatusReturn;
|
|
40
|
+
//#endregion
|
|
41
|
+
//#region src/use-subscribe.d.ts
|
|
42
|
+
declare const useSubscribe: <Api extends AnyFormLikeApi, Selected>(api: Api, selector: ApiSelector<Api, Selected>) => Selected;
|
|
43
|
+
//#endregion
|
|
44
|
+
//#region src/array-field.d.ts
|
|
45
|
+
type ArrayFieldProps<Form extends AnyFormApi, Name extends FormArrayFields<Form>> = FieldOptions<Form, Name> & {
|
|
46
|
+
children: React.ReactNode | ((field: UseArrayFieldReturn<FormFieldValue<Form, Name>>) => React.ReactNode);
|
|
47
|
+
};
|
|
48
|
+
declare const ArrayField: <Form extends AnyFormApi, const Name extends FormArrayFields<Form>>({
|
|
49
|
+
children,
|
|
50
|
+
...options
|
|
51
|
+
}: ArrayFieldProps<Form, Name>) => any;
|
|
52
|
+
//#endregion
|
|
53
|
+
//#region src/field.d.ts
|
|
54
|
+
type FieldProps<Form extends AnyFormApi, Name extends FormFields<Form>> = FieldOptions<Form, Name> & {
|
|
55
|
+
children: React.ReactNode | ((field: UseFieldReturn<FormFieldValue<Form, Name>>) => React.ReactNode);
|
|
56
|
+
};
|
|
57
|
+
declare const Field: <Form extends AnyFormApi, const Name extends FormFields<Form>>({
|
|
58
|
+
children,
|
|
59
|
+
...options
|
|
60
|
+
}: FieldProps<Form, Name>) => any;
|
|
61
|
+
//#endregion
|
|
62
|
+
//#region src/subscribe.d.ts
|
|
63
|
+
type SubscribeProps<Api extends AnyFormLikeApi, Selected> = {
|
|
64
|
+
api: Api;
|
|
65
|
+
selector: ApiSelector<Api, Selected>;
|
|
66
|
+
} & {
|
|
67
|
+
children: React.ReactNode | ((field: Selected) => React.ReactNode);
|
|
68
|
+
};
|
|
69
|
+
declare const Subscribe: <Api extends AnyFormLikeApi, Selected>({
|
|
70
|
+
api,
|
|
71
|
+
selector,
|
|
72
|
+
children
|
|
73
|
+
}: SubscribeProps<Api, Selected>) => react0.ReactNode;
|
|
74
|
+
//#endregion
|
|
75
|
+
export { ArrayField, type ArrayFieldProps, Field, type FieldProps, Subscribe, type SubscribeProps, type UseArrayFieldReturn, type UseFieldApiReturn, type UseFieldReturn, type UseFormReturn, type UseFormStatusProps, type UseFormStatusReturn, useArrayField, useField, useFieldApi, useForm, useFormStatus, useSubscribe };
|
|
76
|
+
//# sourceMappingURL=index.d.cts.map
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { AnyFormApi, AnyFormLikeApi, ApiSelector, ArrayFieldApi, ArrayFieldOptions, ArrayLike, EventLike, FieldApi, FieldOptions, FieldState, FormApi, FormArrayFields, FormFieldValue, FormFields, FormOptions, FormStatus } from "oxform-core";
|
|
2
|
+
import * as react0 from "react";
|
|
3
|
+
export * from "oxform-core";
|
|
4
|
+
|
|
5
|
+
//#region src/use-array-field.d.ts
|
|
6
|
+
type UseArrayFieldReturn<Value extends ArrayLike> = Omit<ArrayFieldApi<Value>, '~mount' | '~update'> & {
|
|
7
|
+
fields: Value;
|
|
8
|
+
};
|
|
9
|
+
declare const useArrayField: <Form extends AnyFormApi, const Name extends FormArrayFields<Form>>(options: ArrayFieldOptions<Form, Name>) => UseArrayFieldReturn<FormFieldValue<Form, Name>>;
|
|
10
|
+
//#endregion
|
|
11
|
+
//#region src/use-field.d.ts
|
|
12
|
+
type UseFieldReturn<Value> = Omit<FieldApi<Value>, '~mount' | '~update' | 'state'> & {
|
|
13
|
+
state: FieldState<Value>;
|
|
14
|
+
props: {
|
|
15
|
+
value: Value;
|
|
16
|
+
ref: (element: HTMLElement | null) => void;
|
|
17
|
+
onChange: (event: EventLike) => void;
|
|
18
|
+
onBlur: (event: EventLike) => void;
|
|
19
|
+
onFocus: (event: EventLike) => void;
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
declare const useField: <Form extends AnyFormApi, const Name extends FormFields<Form>>(options: FieldOptions<Form, Name>) => UseFieldReturn<FormFieldValue<Form, Name>>;
|
|
23
|
+
//#endregion
|
|
24
|
+
//#region src/use-field-api.d.ts
|
|
25
|
+
type UseFieldApiReturn<Value> = FieldApi<Value>;
|
|
26
|
+
declare const useFieldApi: <Form extends AnyFormApi, const Name extends FormFields<Form>>(options: FieldOptions<Form, Name>) => UseFieldApiReturn<FormFieldValue<Form, Name>>;
|
|
27
|
+
//#endregion
|
|
28
|
+
//#region src/use-form.d.ts
|
|
29
|
+
type UseFormReturn<Values> = FormApi<Values>;
|
|
30
|
+
declare const useForm: <Values>(options: FormOptions<Values>) => UseFormReturn<Values>;
|
|
31
|
+
//#endregion
|
|
32
|
+
//#region src/use-form-status.d.ts
|
|
33
|
+
type UseFormStatusProps<Values> = {
|
|
34
|
+
form: FormApi<Values>;
|
|
35
|
+
};
|
|
36
|
+
type UseFormStatusReturn = FormStatus;
|
|
37
|
+
declare const useFormStatus: <Values>({
|
|
38
|
+
form
|
|
39
|
+
}: UseFormStatusProps<Values>) => UseFormStatusReturn;
|
|
40
|
+
//#endregion
|
|
41
|
+
//#region src/use-subscribe.d.ts
|
|
42
|
+
declare const useSubscribe: <Api extends AnyFormLikeApi, Selected>(api: Api, selector: ApiSelector<Api, Selected>) => Selected;
|
|
43
|
+
//#endregion
|
|
44
|
+
//#region src/array-field.d.ts
|
|
45
|
+
type ArrayFieldProps<Form extends AnyFormApi, Name extends FormArrayFields<Form>> = FieldOptions<Form, Name> & {
|
|
46
|
+
children: React.ReactNode | ((field: UseArrayFieldReturn<FormFieldValue<Form, Name>>) => React.ReactNode);
|
|
47
|
+
};
|
|
48
|
+
declare const ArrayField: <Form extends AnyFormApi, const Name extends FormArrayFields<Form>>({
|
|
49
|
+
children,
|
|
50
|
+
...options
|
|
51
|
+
}: ArrayFieldProps<Form, Name>) => any;
|
|
52
|
+
//#endregion
|
|
53
|
+
//#region src/field.d.ts
|
|
54
|
+
type FieldProps<Form extends AnyFormApi, Name extends FormFields<Form>> = FieldOptions<Form, Name> & {
|
|
55
|
+
children: React.ReactNode | ((field: UseFieldReturn<FormFieldValue<Form, Name>>) => React.ReactNode);
|
|
56
|
+
};
|
|
57
|
+
declare const Field: <Form extends AnyFormApi, const Name extends FormFields<Form>>({
|
|
58
|
+
children,
|
|
59
|
+
...options
|
|
60
|
+
}: FieldProps<Form, Name>) => any;
|
|
61
|
+
//#endregion
|
|
62
|
+
//#region src/subscribe.d.ts
|
|
63
|
+
type SubscribeProps<Api extends AnyFormLikeApi, Selected> = {
|
|
64
|
+
api: Api;
|
|
65
|
+
selector: ApiSelector<Api, Selected>;
|
|
66
|
+
} & {
|
|
67
|
+
children: React.ReactNode | ((field: Selected) => React.ReactNode);
|
|
68
|
+
};
|
|
69
|
+
declare const Subscribe: <Api extends AnyFormLikeApi, Selected>({
|
|
70
|
+
api,
|
|
71
|
+
selector,
|
|
72
|
+
children
|
|
73
|
+
}: SubscribeProps<Api, Selected>) => react0.ReactNode;
|
|
74
|
+
//#endregion
|
|
75
|
+
export { ArrayField, type ArrayFieldProps, Field, type FieldProps, Subscribe, type SubscribeProps, type UseArrayFieldReturn, type UseFieldApiReturn, type UseFieldReturn, type UseFormReturn, type UseFormStatusProps, type UseFormStatusReturn, useArrayField, useField, useFieldApi, useForm, useFormStatus, useSubscribe };
|
|
76
|
+
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { ArrayFieldApi, FieldApi, FormApi, createArrayFieldApi, createFieldApi } from "oxform-core";
|
|
2
|
+
import { useEffect, useLayoutEffect, useMemo, useState } from "react";
|
|
3
|
+
import { useStore } from "@tanstack/react-store";
|
|
4
|
+
|
|
5
|
+
//#region src/use-isomorphic-layout-effect.ts
|
|
6
|
+
const useIsomorphicLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect;
|
|
7
|
+
|
|
8
|
+
//#endregion
|
|
9
|
+
//#region src/use-subscribe.ts
|
|
10
|
+
const useSubscribe = (api, selector) => {
|
|
11
|
+
return useStore(api.store(), selector);
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
//#endregion
|
|
15
|
+
//#region src/use-array-field.ts
|
|
16
|
+
const useArrayField = (options) => {
|
|
17
|
+
const [api] = useState(() => {
|
|
18
|
+
return createArrayFieldApi({ ...options });
|
|
19
|
+
});
|
|
20
|
+
useIsomorphicLayoutEffect(api["~mount"], [api]);
|
|
21
|
+
useIsomorphicLayoutEffect(() => {
|
|
22
|
+
api["~update"](options);
|
|
23
|
+
});
|
|
24
|
+
return useMemo(() => {
|
|
25
|
+
return {
|
|
26
|
+
...api,
|
|
27
|
+
get fields() {
|
|
28
|
+
return api.state().value;
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
}, [api, useSubscribe(api, (state) => Object.keys(state.value ?? []).length)]);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
//#endregion
|
|
35
|
+
//#region src/use-field-api.ts
|
|
36
|
+
const useFieldApi = (options) => {
|
|
37
|
+
const [api] = useState(() => {
|
|
38
|
+
return createFieldApi(options);
|
|
39
|
+
});
|
|
40
|
+
useIsomorphicLayoutEffect(api["~mount"], [api]);
|
|
41
|
+
useIsomorphicLayoutEffect(() => {
|
|
42
|
+
api["~update"](options);
|
|
43
|
+
});
|
|
44
|
+
return api;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
//#endregion
|
|
48
|
+
//#region src/use-field.ts
|
|
49
|
+
const useField = (options) => {
|
|
50
|
+
const api = useFieldApi(options);
|
|
51
|
+
const value = useStore(api.store(), (state) => state.value);
|
|
52
|
+
const defaultValue = useStore(api.store(), (state) => state.defaultValue);
|
|
53
|
+
const dirty = useStore(api.store(), (state) => state.meta.dirty);
|
|
54
|
+
const touched = useStore(api.store(), (state) => state.meta.touched);
|
|
55
|
+
const blurred = useStore(api.store(), (state) => state.meta.blurred);
|
|
56
|
+
const pristine = useStore(api.store(), (state) => state.meta.pristine);
|
|
57
|
+
const valid = useStore(api.store(), (state) => state.meta.valid);
|
|
58
|
+
const isDefault = useStore(api.store(), (state) => state.meta.default);
|
|
59
|
+
const errors = useStore(api.store(), (state) => state.errors);
|
|
60
|
+
return useMemo(() => {
|
|
61
|
+
return {
|
|
62
|
+
...api,
|
|
63
|
+
state: {
|
|
64
|
+
value,
|
|
65
|
+
defaultValue,
|
|
66
|
+
errors,
|
|
67
|
+
meta: {
|
|
68
|
+
blurred,
|
|
69
|
+
default: isDefault,
|
|
70
|
+
dirty,
|
|
71
|
+
pristine,
|
|
72
|
+
touched,
|
|
73
|
+
valid
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
props: {
|
|
77
|
+
value,
|
|
78
|
+
onBlur: api.blur,
|
|
79
|
+
onFocus: api.focus,
|
|
80
|
+
onChange: (event) => api.change(event.target?.value),
|
|
81
|
+
ref: api.register
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
}, [
|
|
85
|
+
api,
|
|
86
|
+
errors,
|
|
87
|
+
value,
|
|
88
|
+
defaultValue,
|
|
89
|
+
dirty,
|
|
90
|
+
touched,
|
|
91
|
+
blurred,
|
|
92
|
+
pristine,
|
|
93
|
+
valid,
|
|
94
|
+
isDefault
|
|
95
|
+
]);
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
//#endregion
|
|
99
|
+
//#region src/use-form.ts
|
|
100
|
+
const useForm = (options) => {
|
|
101
|
+
const [api] = useState(() => {
|
|
102
|
+
return new FormApi({ ...options });
|
|
103
|
+
});
|
|
104
|
+
useIsomorphicLayoutEffect(api["~mount"], [api]);
|
|
105
|
+
useIsomorphicLayoutEffect(() => {
|
|
106
|
+
api["~update"](options);
|
|
107
|
+
});
|
|
108
|
+
return api;
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
//#endregion
|
|
112
|
+
//#region src/use-form-status.ts
|
|
113
|
+
const useFormStatus = ({ form }) => {
|
|
114
|
+
const dirty = useStore(form.store(), (state) => state.status.dirty);
|
|
115
|
+
const valid = useStore(form.store(), (state) => state.status.valid);
|
|
116
|
+
const submitting = useStore(form.store(), (state) => state.status.submitting);
|
|
117
|
+
const successful = useStore(form.store(), (state) => state.status.successful);
|
|
118
|
+
const validating = useStore(form.store(), (state) => state.status.validating);
|
|
119
|
+
const submits = useStore(form.store(), (state) => state.status.submits);
|
|
120
|
+
const submitted = useStore(form.store(), (state) => state.status.submitted);
|
|
121
|
+
return useMemo(() => {
|
|
122
|
+
return {
|
|
123
|
+
dirty,
|
|
124
|
+
valid,
|
|
125
|
+
submitting,
|
|
126
|
+
successful,
|
|
127
|
+
validating,
|
|
128
|
+
submits,
|
|
129
|
+
submitted
|
|
130
|
+
};
|
|
131
|
+
}, [
|
|
132
|
+
dirty,
|
|
133
|
+
valid,
|
|
134
|
+
submitting,
|
|
135
|
+
successful,
|
|
136
|
+
validating,
|
|
137
|
+
submits,
|
|
138
|
+
submitted
|
|
139
|
+
]);
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
//#endregion
|
|
143
|
+
//#region src/array-field.tsx
|
|
144
|
+
const ArrayField = ({ children, ...options }) => {
|
|
145
|
+
const field = useArrayField(options);
|
|
146
|
+
return useMemo(() => {
|
|
147
|
+
return typeof children === "function" ? children(field) : children;
|
|
148
|
+
}, [children, field]);
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
//#endregion
|
|
152
|
+
//#region src/field.tsx
|
|
153
|
+
const Field = ({ children, ...options }) => {
|
|
154
|
+
const field = useField(options);
|
|
155
|
+
return useMemo(() => {
|
|
156
|
+
return typeof children === "function" ? children(field) : children;
|
|
157
|
+
}, [children, field]);
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
//#endregion
|
|
161
|
+
//#region src/subscribe.tsx
|
|
162
|
+
const Subscribe = ({ api, selector, children }) => {
|
|
163
|
+
const selected = useSubscribe(api, selector);
|
|
164
|
+
return useMemo(() => {
|
|
165
|
+
return typeof children === "function" ? children(selected) : children;
|
|
166
|
+
}, [children, selected]);
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
//#endregion
|
|
170
|
+
export { ArrayField, Field, Subscribe, useArrayField, useField, useFieldApi, useForm, useFormStatus, useSubscribe };
|
|
171
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/use-isomorphic-layout-effect.ts","../src/use-subscribe.ts","../src/use-array-field.ts","../src/use-field-api.ts","../src/use-field.ts","../src/use-form.ts","../src/use-form-status.ts","../src/array-field.tsx","../src/field.tsx","../src/subscribe.tsx"],"sourcesContent":["import { useEffect, useLayoutEffect } from 'react';\n\nexport const useIsomorphicLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect;\n","import { useStore } from '@tanstack/react-store';\nimport { type AnyFormLikeApi, type ApiSelector } from 'oxform-core';\n\nexport const useSubscribe = <Api extends AnyFormLikeApi, Selected>(api: Api, selector: ApiSelector<Api, Selected>) => {\n return useStore(api.store() as never, selector as never) as Selected;\n};\n\n// const schema = z.object({ name: z.string(), names: z.string().array() });\n// const form = new FormApi({ schema, defaultValues: { name: 'John', names: ['Jane', 'Bob'] } });\n// const field = new FieldApi({ form, name: 'name' });\n// const array = new ArrayFieldApi({ form, name: 'names' });\n\n// const a = useSubscribe(form, state => state.values);\n// const b = useSubscribe(field, state => state.value);\n// const c = useSubscribe(array, state => state.value);\n","import { ArrayFieldApi, createArrayFieldApi } from 'oxform-core';\n\nimport { useIsomorphicLayoutEffect } from '#use-isomorphic-layout-effect';\nimport { useSubscribe } from '#use-subscribe';\nimport type { AnyFormApi, ArrayFieldOptions, ArrayLike, FormArrayFields, FormFieldValue } from 'oxform-core';\nimport { useMemo, useState } from 'react';\n\nexport type UseArrayFieldReturn<Value extends ArrayLike> = Omit<ArrayFieldApi<Value>, '~mount' | '~update'> & {\n fields: Value;\n};\n\nexport const useArrayField = <Form extends AnyFormApi, const Name extends FormArrayFields<Form>>(\n options: ArrayFieldOptions<Form, Name>,\n): UseArrayFieldReturn<FormFieldValue<Form, Name>> => {\n const [api] = useState(() => {\n return createArrayFieldApi({ ...options });\n });\n\n useIsomorphicLayoutEffect(api['~mount'], [api]);\n useIsomorphicLayoutEffect(() => {\n api['~update'](options);\n });\n\n // todo: re-create api if form or name changes\n // spike: use optional context to cache the api instance\n\n const length = useSubscribe(api, state => Object.keys((state.value as unknown) ?? []).length);\n\n return useMemo(() => {\n void length;\n\n return {\n ...api,\n get fields() {\n return api.state().value;\n },\n } satisfies UseArrayFieldReturn<FormFieldValue<Form, Name>>;\n }, [api, length]);\n};\n","import { createFieldApi, FieldApi, type FieldOptions } from 'oxform-core';\n\nimport { useIsomorphicLayoutEffect } from '#use-isomorphic-layout-effect';\nimport type { AnyFormApi, FormFields, FormFieldValue } from 'oxform-core';\nimport { useState } from 'react';\n\nexport type UseFieldApiReturn<Value> = FieldApi<Value>;\n\nexport const useFieldApi = <Form extends AnyFormApi, const Name extends FormFields<Form>>(\n options: FieldOptions<Form, Name>,\n): UseFieldApiReturn<FormFieldValue<Form, Name>> => {\n const [api] = useState(() => {\n return createFieldApi(options);\n });\n\n // todo: re-create api if form or name changes\n // spike: use optional context to cache the api instance\n\n useIsomorphicLayoutEffect(api['~mount'], [api]);\n useIsomorphicLayoutEffect(() => {\n api['~update'](options);\n });\n\n return api;\n};\n","import type { AnyFormApi, EventLike, FieldState, FormFields, FormFieldValue } from 'oxform-core';\nimport { FieldApi, type FieldOptions } from 'oxform-core';\n\nimport { useFieldApi } from '#use-field-api';\nimport { useStore } from '@tanstack/react-store';\nimport { useMemo } from 'react';\n\nexport type UseFieldReturn<Value> = Omit<FieldApi<Value>, '~mount' | '~update' | 'state'> & {\n state: FieldState<Value>;\n props: {\n value: Value;\n ref: (element: HTMLElement | null) => void;\n onChange: (event: EventLike) => void;\n onBlur: (event: EventLike) => void;\n onFocus: (event: EventLike) => void;\n };\n};\n\nexport const useField = <Form extends AnyFormApi, const Name extends FormFields<Form>>(\n options: FieldOptions<Form, Name>,\n): UseFieldReturn<FormFieldValue<Form, Name>> => {\n const api = useFieldApi(options);\n\n const value = useStore(api.store(), state => state.value);\n const defaultValue = useStore(api.store(), state => state.defaultValue);\n const dirty = useStore(api.store(), state => state.meta.dirty);\n const touched = useStore(api.store(), state => state.meta.touched);\n const blurred = useStore(api.store(), state => state.meta.blurred);\n const pristine = useStore(api.store(), state => state.meta.pristine);\n const valid = useStore(api.store(), state => state.meta.valid);\n const isDefault = useStore(api.store(), state => state.meta.default);\n const errors = useStore(api.store(), state => state.errors);\n\n return useMemo(() => {\n return {\n ...api,\n\n state: {\n value,\n defaultValue,\n errors,\n meta: {\n blurred,\n default: isDefault,\n dirty,\n pristine,\n touched,\n valid,\n },\n },\n\n props: {\n value,\n onBlur: api.blur,\n onFocus: api.focus,\n onChange: (event: EventLike) => api.change(event.target?.value),\n ref: api.register,\n },\n } satisfies UseFieldReturn<any> as any;\n }, [api, errors, value, defaultValue, dirty, touched, blurred, pristine, valid, isDefault]);\n};\n","import { useIsomorphicLayoutEffect } from '#use-isomorphic-layout-effect';\nimport type { FormOptions } from 'oxform-core';\nimport { FormApi } from 'oxform-core';\nimport { useState } from 'react';\n\nexport type UseFormReturn<Values> = FormApi<Values>;\n\nexport const useForm = <Values>(options: FormOptions<Values>) => {\n const [api] = useState(() => {\n return new FormApi({ ...options });\n });\n\n useIsomorphicLayoutEffect(api['~mount'], [api]);\n useIsomorphicLayoutEffect(() => {\n api['~update'](options);\n });\n\n // todo: re-create api if id changes\n\n return api satisfies UseFormReturn<Values> as UseFormReturn<Values>;\n};\n","import { useStore } from '@tanstack/react-store';\nimport type { FormApi, FormStatus } from 'oxform-core';\nimport { useMemo } from 'react';\n\nexport type UseFormStatusProps<Values> = {\n form: FormApi<Values>;\n};\n\nexport type UseFormStatusReturn = FormStatus;\n\nexport const useFormStatus = <Values>({ form }: UseFormStatusProps<Values>): UseFormStatusReturn => {\n const dirty = useStore(form.store(), state => state.status.dirty);\n const valid = useStore(form.store(), state => state.status.valid);\n const submitting = useStore(form.store(), state => state.status.submitting);\n const successful = useStore(form.store(), state => state.status.successful);\n const validating = useStore(form.store(), state => state.status.validating);\n const submits = useStore(form.store(), state => state.status.submits);\n const submitted = useStore(form.store(), state => state.status.submitted);\n\n return useMemo(() => {\n return {\n dirty,\n valid,\n submitting,\n successful,\n validating,\n submits,\n submitted,\n } satisfies UseFormStatusReturn;\n }, [dirty, valid, submitting, successful, validating, submits, submitted]);\n};\n","import { useArrayField, type UseArrayFieldReturn } from '#use-array-field';\nimport type { AnyFormApi, FieldOptions, FormArrayFields, FormFieldValue } from 'oxform-core';\nimport { useMemo } from 'react';\n\nexport type ArrayFieldProps<Form extends AnyFormApi, Name extends FormArrayFields<Form>> = FieldOptions<Form, Name> & {\n children: React.ReactNode | ((field: UseArrayFieldReturn<FormFieldValue<Form, Name>>) => React.ReactNode);\n};\n\nexport const ArrayField = <Form extends AnyFormApi, const Name extends FormArrayFields<Form>>({\n children,\n ...options\n}: ArrayFieldProps<Form, Name>) => {\n const field = useArrayField(options);\n\n return useMemo(() => {\n return typeof children === 'function' ? children(field) : children;\n }, [children, field]);\n};\n","import { useField, type UseFieldReturn } from '#use-field';\nimport type { AnyFormApi, FieldOptions, FormFields, FormFieldValue } from 'oxform-core';\nimport { useMemo } from 'react';\n\nexport type FieldProps<Form extends AnyFormApi, Name extends FormFields<Form>> = FieldOptions<Form, Name> & {\n children: React.ReactNode | ((field: UseFieldReturn<FormFieldValue<Form, Name>>) => React.ReactNode);\n};\n\nexport const Field = <Form extends AnyFormApi, const Name extends FormFields<Form>>({\n children,\n ...options\n}: FieldProps<Form, Name>) => {\n const field = useField(options);\n\n return useMemo(() => {\n return typeof children === 'function' ? children(field) : children;\n }, [children, field]);\n};\n","import { useSubscribe } from '#use-subscribe';\nimport type { AnyFormLikeApi, ApiSelector } from 'oxform-core';\nimport { useMemo } from 'react';\n\nexport type SubscribeProps<Api extends AnyFormLikeApi, Selected> = {\n api: Api;\n selector: ApiSelector<Api, Selected>;\n} & {\n children: React.ReactNode | ((field: Selected) => React.ReactNode);\n};\n\nexport const Subscribe = <Api extends AnyFormLikeApi, Selected>({\n api,\n selector,\n children,\n}: SubscribeProps<Api, Selected>) => {\n const selected = useSubscribe(api, selector);\n\n return useMemo(() => {\n return typeof children === 'function' ? children(selected as never) : children;\n }, [children, selected]);\n};\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,43 +1,49 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "oxform-react",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Oxform is a lean and flexible framework-agnostic form library.",
|
|
5
5
|
"author": "Frantss <frantss.bongiovanni@gmail.com>",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"sideEffects": false,
|
|
8
8
|
"type": "module",
|
|
9
|
-
"main": "./dist/index.
|
|
9
|
+
"main": "./dist/index.cjs",
|
|
10
10
|
"imports": {
|
|
11
|
-
"#*":
|
|
12
|
-
"./src/*.ts",
|
|
13
|
-
"./src/**/*.ts"
|
|
14
|
-
]
|
|
11
|
+
"#*": "./src/*.ts"
|
|
15
12
|
},
|
|
16
13
|
"exports": {
|
|
17
|
-
".":
|
|
14
|
+
".": {
|
|
15
|
+
"import": "./dist/index.js",
|
|
16
|
+
"require": "./dist/index.cjs"
|
|
17
|
+
},
|
|
18
18
|
"./package.json": "./package.json"
|
|
19
19
|
},
|
|
20
20
|
"module": "./dist/index.js",
|
|
21
|
-
"types": "./dist/index.d.
|
|
21
|
+
"types": "./dist/index.d.cts",
|
|
22
|
+
"files": [
|
|
23
|
+
"dist"
|
|
24
|
+
],
|
|
22
25
|
"scripts": {
|
|
23
26
|
"build": "tsdown --config-loader unrun",
|
|
24
27
|
"test": "vitest",
|
|
25
|
-
"
|
|
28
|
+
"check:types": "tsc --noEmit",
|
|
29
|
+
"check:package": "publint --strict && attw --pack --no-emoji"
|
|
26
30
|
},
|
|
27
31
|
"dependencies": {
|
|
28
|
-
"@standard-schema/spec": "
|
|
32
|
+
"@standard-schema/spec": "^1.1.0",
|
|
29
33
|
"@tanstack/react-store": "^0.8.0",
|
|
30
|
-
"oxform-core": "
|
|
34
|
+
"oxform-core": "0.1.1"
|
|
31
35
|
},
|
|
32
36
|
"devDependencies": {
|
|
37
|
+
"@arethetypeswrong/cli": "^0.18.2",
|
|
33
38
|
"@types/react": "^19.2.7",
|
|
34
39
|
"@types/react-dom": "^19.2.3",
|
|
35
40
|
"@vitejs/plugin-react": "^5.1.2",
|
|
41
|
+
"publint": "^0.3.16",
|
|
36
42
|
"react": "19.2.3",
|
|
37
43
|
"react-dom": "19.2.3",
|
|
38
|
-
"tsdown": "
|
|
39
|
-
"typescript": "
|
|
40
|
-
"vitest": "
|
|
44
|
+
"tsdown": "^0.18.1",
|
|
45
|
+
"typescript": "^5.9.3",
|
|
46
|
+
"vitest": "^4.0.16",
|
|
41
47
|
"vitest-browser-react": "^2.0.2",
|
|
42
48
|
"zod": "^4.2.1"
|
|
43
49
|
},
|
package/export/index.ts
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
// reference: https://stevekinney.com/courses/react-performance/performance-testing-strategy
|
|
2
|
-
|
|
3
|
-
import { Profiler, type ProfilerOnRenderCallback } from 'react';
|
|
4
|
-
import { expect } from 'vitest';
|
|
5
|
-
import { render } from 'vitest-browser-react';
|
|
6
|
-
|
|
7
|
-
interface RenderMetrics {
|
|
8
|
-
renderTime: number;
|
|
9
|
-
commitTime: number;
|
|
10
|
-
actualDuration: number;
|
|
11
|
-
baseDuration: number;
|
|
12
|
-
startTime: number;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export function measureRenderPerformance(component: React.ReactElement, testName: string): Promise<RenderMetrics> {
|
|
16
|
-
return new Promise(resolve => {
|
|
17
|
-
let metrics: RenderMetrics;
|
|
18
|
-
|
|
19
|
-
const onRender: ProfilerOnRenderCallback = (_, phase, actualDuration, baseDuration, startTime, commitTime) => {
|
|
20
|
-
if (phase === 'mount' || phase === 'update') {
|
|
21
|
-
metrics = {
|
|
22
|
-
renderTime: actualDuration,
|
|
23
|
-
commitTime,
|
|
24
|
-
actualDuration,
|
|
25
|
-
baseDuration,
|
|
26
|
-
startTime,
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
void render(
|
|
32
|
-
<Profiler id={testName} onRender={onRender}>
|
|
33
|
-
{component}
|
|
34
|
-
</Profiler>,
|
|
35
|
-
);
|
|
36
|
-
|
|
37
|
-
setTimeout(() => resolve(metrics), 0);
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
expect.extend({
|
|
42
|
-
toRenderWithinTime(received: RenderMetrics, maxTime: number) {
|
|
43
|
-
const pass = received.renderTime <= maxTime;
|
|
44
|
-
|
|
45
|
-
return {
|
|
46
|
-
message: () => `Expected component to render in ${maxTime}ms, but took ${received.renderTime.toFixed(2)}ms`,
|
|
47
|
-
pass,
|
|
48
|
-
};
|
|
49
|
-
},
|
|
50
|
-
|
|
51
|
-
toHaveMaxReRenders(received: number, maxRenders: number) {
|
|
52
|
-
const pass = received <= maxRenders;
|
|
53
|
-
|
|
54
|
-
return {
|
|
55
|
-
message: () => `Expected component to re-render at most ${maxRenders} times, but re-rendered ${received} times`,
|
|
56
|
-
pass,
|
|
57
|
-
};
|
|
58
|
-
},
|
|
59
|
-
});
|
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
import { useField } from '#use-field';
|
|
2
|
-
import { FormApi } from 'oxform-core';
|
|
3
|
-
import 'react';
|
|
4
|
-
import { expect, it, test } from 'vitest';
|
|
5
|
-
import { render } from 'vitest-browser-react';
|
|
6
|
-
import { userEvent } from 'vitest/browser';
|
|
7
|
-
import { z } from 'zod';
|
|
8
|
-
|
|
9
|
-
const setup = async () => {
|
|
10
|
-
const form = new FormApi({
|
|
11
|
-
schema: z.object({
|
|
12
|
-
name: z.string(),
|
|
13
|
-
nested: z.object({
|
|
14
|
-
deep: z.object({
|
|
15
|
-
deeper: z.string().array(),
|
|
16
|
-
}),
|
|
17
|
-
}),
|
|
18
|
-
}),
|
|
19
|
-
defaultValues: {
|
|
20
|
-
name: 'John',
|
|
21
|
-
nested: {
|
|
22
|
-
deep: {
|
|
23
|
-
deeper: ['hello'],
|
|
24
|
-
},
|
|
25
|
-
},
|
|
26
|
-
},
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
const Component = () => {
|
|
30
|
-
const field = useField({ form, name: 'name' });
|
|
31
|
-
|
|
32
|
-
return (
|
|
33
|
-
<>
|
|
34
|
-
<input {...field.props} />
|
|
35
|
-
<button type='button'>outside</button>
|
|
36
|
-
</>
|
|
37
|
-
);
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
const utils = await render(<Component />);
|
|
41
|
-
|
|
42
|
-
const ui = {
|
|
43
|
-
input: utils.getByRole('textbox'),
|
|
44
|
-
outside: utils.getByRole('button'),
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
return { utils, ui, form };
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
it("should update the form's values on change", async () => {
|
|
51
|
-
const { form, ui } = await setup();
|
|
52
|
-
|
|
53
|
-
await userEvent.clear(ui.input);
|
|
54
|
-
await userEvent.type(ui.input, 'Jane');
|
|
55
|
-
|
|
56
|
-
expect(form.store.state.values.name).toBe('Jane');
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
it('should mark the field as touched on focus', async () => {
|
|
60
|
-
const { form, ui } = await setup();
|
|
61
|
-
|
|
62
|
-
await ui.input.click();
|
|
63
|
-
|
|
64
|
-
const meta = form.store.state.fields['name'];
|
|
65
|
-
|
|
66
|
-
expect(meta).toBeDefined();
|
|
67
|
-
expect(meta.touched).toBe(true);
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
it('should mark the fields as blurred on blur', async () => {
|
|
71
|
-
const { form, ui } = await setup();
|
|
72
|
-
|
|
73
|
-
await ui.input.click();
|
|
74
|
-
await ui.outside.click(); // blur the input
|
|
75
|
-
|
|
76
|
-
const meta = form.store.state.fields['name'];
|
|
77
|
-
|
|
78
|
-
expect(meta).toBeDefined();
|
|
79
|
-
expect(meta.blurred).toBe(true);
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
it('should keep the field as pristine when field is not dirty', async () => {
|
|
83
|
-
const { form, ui } = await setup();
|
|
84
|
-
|
|
85
|
-
await ui.input.click();
|
|
86
|
-
await ui.outside.click(); // blur the input
|
|
87
|
-
|
|
88
|
-
const meta = form.store.state.fields['name'];
|
|
89
|
-
|
|
90
|
-
expect(meta).toBeDefined();
|
|
91
|
-
expect(meta.pristine).toBe(true);
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
test('default is true when value is equal to default value', async () => {
|
|
95
|
-
const { form, ui } = await setup();
|
|
96
|
-
|
|
97
|
-
await userEvent.clear(ui.input);
|
|
98
|
-
await userEvent.type(ui.input, 'Jane');
|
|
99
|
-
await userEvent.clear(ui.input);
|
|
100
|
-
await userEvent.type(ui.input, 'John');
|
|
101
|
-
|
|
102
|
-
const meta = form.store.state.fields['name'];
|
|
103
|
-
|
|
104
|
-
expect(meta.default).toBe(true);
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
test('default is false when value is not equal to default value', async () => {
|
|
108
|
-
const { form, ui } = await setup();
|
|
109
|
-
|
|
110
|
-
await userEvent.clear(ui.input);
|
|
111
|
-
await userEvent.type(ui.input, 'Jane');
|
|
112
|
-
|
|
113
|
-
const meta = form.store.state.fields['name'];
|
|
114
|
-
|
|
115
|
-
expect(meta.default).toBe(false);
|
|
116
|
-
});
|
package/src/use-field.ts
DELETED
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
import type { FieldMeta, FormIssue, FormResetFieldOptions, FormSetErrorsOptions, ValidateOptions } from 'oxform-core';
|
|
2
|
-
import { FieldApi, type FieldOptions, type FieldProps } from 'oxform-core';
|
|
3
|
-
|
|
4
|
-
import { useIsomorphicLayoutEffect } from '#use-isomorphic-layout-effect';
|
|
5
|
-
import { useStore } from '@tanstack/react-store';
|
|
6
|
-
import type { DeepKeys, DeepValue, EventLike } from 'oxform-core';
|
|
7
|
-
import type { StandardSchema } from 'oxform-core/schema';
|
|
8
|
-
import { useCallback, useMemo, useState } from 'react';
|
|
9
|
-
|
|
10
|
-
export type UseFieldReturn<Value> = {
|
|
11
|
-
api: FieldApi<any, any, Value>;
|
|
12
|
-
value: Value;
|
|
13
|
-
change: (value: Value) => void;
|
|
14
|
-
blur: () => void;
|
|
15
|
-
focus: () => void;
|
|
16
|
-
register: () => void;
|
|
17
|
-
validate: (options?: ValidateOptions) => Promise<FormIssue[]>;
|
|
18
|
-
reset: (options?: FormResetFieldOptions<Value>) => void;
|
|
19
|
-
setErrors: (errors: FormIssue[], options?: FormSetErrorsOptions) => void;
|
|
20
|
-
props: FieldProps<Value>;
|
|
21
|
-
meta: FieldMeta;
|
|
22
|
-
errors: FormIssue[];
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
export const useField = <Schema extends StandardSchema, Name extends DeepKeys<StandardSchema.InferInput<Schema>>>(
|
|
26
|
-
options: FieldOptions<Schema, Name>,
|
|
27
|
-
) => {
|
|
28
|
-
type Value = DeepValue<StandardSchema.InferInput<Schema>, Name>;
|
|
29
|
-
|
|
30
|
-
const [api] = useState(() => {
|
|
31
|
-
return new FieldApi({ ...options });
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
useIsomorphicLayoutEffect(api['~mount'], [api]);
|
|
35
|
-
useIsomorphicLayoutEffect(() => {
|
|
36
|
-
api['~update'](options);
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
const value = useStore(api.store, state => state.value);
|
|
40
|
-
const defaultValue = useStore(api.store, state => state.defaultValue);
|
|
41
|
-
const dirty = useStore(api.store, state => state.meta.dirty);
|
|
42
|
-
const touched = useStore(api.store, state => state.meta.touched);
|
|
43
|
-
const blurred = useStore(api.store, state => state.meta.blurred);
|
|
44
|
-
const pristine = useStore(api.store, state => state.meta.pristine);
|
|
45
|
-
const valid = useStore(api.store, state => state.meta.valid);
|
|
46
|
-
const isDefault = useStore(api.store, state => state.meta.default);
|
|
47
|
-
const errors = useStore(api.store, state => state.errors);
|
|
48
|
-
|
|
49
|
-
const onChange = useCallback((event: EventLike) => api.change(event.target?.value), [api]);
|
|
50
|
-
|
|
51
|
-
return useMemo(() => {
|
|
52
|
-
return {
|
|
53
|
-
api,
|
|
54
|
-
value,
|
|
55
|
-
blur: api.blur,
|
|
56
|
-
focus: api.focus,
|
|
57
|
-
change: api.change,
|
|
58
|
-
register: api.register,
|
|
59
|
-
validate: api.validate,
|
|
60
|
-
reset: api.reset,
|
|
61
|
-
setErrors: api.setErrors,
|
|
62
|
-
errors,
|
|
63
|
-
props: {
|
|
64
|
-
value,
|
|
65
|
-
defaultValue,
|
|
66
|
-
ref: api.register(),
|
|
67
|
-
onBlur: api.blur,
|
|
68
|
-
onFocus: api.focus,
|
|
69
|
-
onChange,
|
|
70
|
-
},
|
|
71
|
-
meta: {
|
|
72
|
-
dirty,
|
|
73
|
-
touched,
|
|
74
|
-
blurred,
|
|
75
|
-
pristine,
|
|
76
|
-
valid,
|
|
77
|
-
default: isDefault,
|
|
78
|
-
},
|
|
79
|
-
} satisfies UseFieldReturn<Value> as UseFieldReturn<Value>;
|
|
80
|
-
}, [api, errors, value, defaultValue, onChange, dirty, touched, blurred, pristine, valid, isDefault]);
|
|
81
|
-
};
|
package/src/use-form-status.ts
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { useStore } from '@tanstack/react-store';
|
|
2
|
-
import type { FormApi, FormStatus } from 'oxform-core';
|
|
3
|
-
import type { StandardSchema } from 'oxform-core/schema';
|
|
4
|
-
import { useMemo } from 'react';
|
|
5
|
-
|
|
6
|
-
export type UseFormStatusReturn = FormStatus;
|
|
7
|
-
|
|
8
|
-
export const useFormStatus = <Schema extends StandardSchema>({ form }: { form: FormApi<Schema> }) => {
|
|
9
|
-
const dirty = useStore(form.store, state => state.status.dirty);
|
|
10
|
-
const valid = useStore(form.store, state => state.status.valid);
|
|
11
|
-
const submitting = useStore(form.store, state => state.status.submitting);
|
|
12
|
-
const successful = useStore(form.store, state => state.status.successful);
|
|
13
|
-
const validating = useStore(form.store, state => state.status.validating);
|
|
14
|
-
const submits = useStore(form.store, state => state.status.submits);
|
|
15
|
-
const submitted = useStore(form.store, state => state.status.submitted);
|
|
16
|
-
|
|
17
|
-
return useMemo(() => {
|
|
18
|
-
return {
|
|
19
|
-
dirty,
|
|
20
|
-
valid,
|
|
21
|
-
submitting,
|
|
22
|
-
successful,
|
|
23
|
-
validating,
|
|
24
|
-
submits,
|
|
25
|
-
submitted,
|
|
26
|
-
} satisfies UseFormStatusReturn as UseFormStatusReturn;
|
|
27
|
-
}, [dirty, valid, submitting, successful, validating, submits, submitted]);
|
|
28
|
-
};
|
package/src/use-form.ts
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { useIsomorphicLayoutEffect } from '#use-isomorphic-layout-effect';
|
|
2
|
-
import type { FormOptions } from 'oxform-core';
|
|
3
|
-
import { FormApi } from 'oxform-core';
|
|
4
|
-
import type { StandardSchema } from 'oxform-core/schema';
|
|
5
|
-
import { useState } from 'react';
|
|
6
|
-
|
|
7
|
-
export type UseFormReturn<Schema extends StandardSchema> = FormApi<Schema>;
|
|
8
|
-
|
|
9
|
-
export const useForm = <Schema extends StandardSchema>(options: FormOptions<Schema>) => {
|
|
10
|
-
const [api] = useState(() => {
|
|
11
|
-
return new FormApi({ ...options });
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
useIsomorphicLayoutEffect(api['~mount'], [api]);
|
|
15
|
-
useIsomorphicLayoutEffect(() => {
|
|
16
|
-
api['~update'](options);
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
return api satisfies UseFormReturn<Schema> as UseFormReturn<Schema>;
|
|
20
|
-
};
|
package/tsconfig.json
DELETED
package/tsdown.config.ts
DELETED
package/vitest.config.ts
DELETED