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 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":"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"}
@@ -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
@@ -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.0",
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.js",
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
- ".": "./dist/index.js",
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.ts",
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
- "lint:types": "tsc --noEmit"
28
+ "check:types": "tsc --noEmit",
29
+ "check:package": "publint --strict && attw --pack --no-emoji"
26
30
  },
27
31
  "dependencies": {
28
- "@standard-schema/spec": "catalog:oxform",
32
+ "@standard-schema/spec": "^1.1.0",
29
33
  "@tanstack/react-store": "^0.8.0",
30
- "oxform-core": "workspace:*"
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": "catalog:oxform",
39
- "typescript": "catalog:oxform",
40
- "vitest": "catalog:oxform",
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,3 +0,0 @@
1
- export { useField } from '#use-field';
2
- export type { UseFieldReturn } from '#use-field';
3
- export { useForm } from '#use-form';
@@ -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
- };
@@ -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
- };
@@ -1,3 +0,0 @@
1
- import { useEffect, useLayoutEffect } from 'react';
2
-
3
- export const useIsomorphicLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect;
package/tsconfig.json DELETED
@@ -1,6 +0,0 @@
1
- {
2
- "extends": "../../tsconfig.json",
3
- "compilerOptions": {
4
- "jsx": "react-jsx"
5
- }
6
- }
package/tsdown.config.ts DELETED
@@ -1,9 +0,0 @@
1
- import { defineConfig } from 'tsdown';
2
- import base from '../../tsdown.config';
3
-
4
- export default defineConfig({
5
- ...base,
6
- entry: {
7
- index: 'export/index.ts',
8
- },
9
- });
package/vitest.config.ts DELETED
@@ -1,10 +0,0 @@
1
- import react from '@vitejs/plugin-react';
2
- import { defineConfig, mergeConfig } from 'vitest/config';
3
- import base from '../../vitest.config';
4
-
5
- export default mergeConfig(
6
- base,
7
- defineConfig({
8
- plugins: [react()],
9
- }),
10
- );