@octanejs/hook-form 0.1.0
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/LICENSE +21 -0
- package/README.md +62 -0
- package/package.json +46 -0
- package/src/FormProvider.tsrx +76 -0
- package/src/FormProvider.tsrx.d.ts +10 -0
- package/src/constants.ts +31 -0
- package/src/controller.tsrx +8 -0
- package/src/controller.tsrx.d.ts +52 -0
- package/src/form.tsrx +119 -0
- package/src/form.tsrx.d.ts +6 -0
- package/src/formStateSubscribe.tsrx +10 -0
- package/src/formStateSubscribe.tsrx.d.ts +16 -0
- package/src/index.ts +19 -0
- package/src/logic/appendErrors.ts +19 -0
- package/src/logic/createFormControl.ts +1841 -0
- package/src/logic/generateId.ts +14 -0
- package/src/logic/generateWatchOutput.ts +27 -0
- package/src/logic/getCheckboxValue.ts +36 -0
- package/src/logic/getDirtyFields.ts +120 -0
- package/src/logic/getEventValue.ts +12 -0
- package/src/logic/getFieldValue.ts +33 -0
- package/src/logic/getFieldValueAs.ts +22 -0
- package/src/logic/getFocusFieldName.ts +13 -0
- package/src/logic/getNodeParentName.ts +4 -0
- package/src/logic/getProxyFormState.ts +33 -0
- package/src/logic/getRadioValue.ts +24 -0
- package/src/logic/getResolverOptions.ts +33 -0
- package/src/logic/getRuleValue.ts +16 -0
- package/src/logic/getValidateError.ts +22 -0
- package/src/logic/getValidationModes.ts +11 -0
- package/src/logic/getValueAndMessage.ts +12 -0
- package/src/logic/hasPromiseValidation.ts +27 -0
- package/src/logic/hasValidation.ts +12 -0
- package/src/logic/index.ts +3 -0
- package/src/logic/isNameInFieldArray.ts +7 -0
- package/src/logic/isWatched.ts +11 -0
- package/src/logic/iterateFieldsByAction.ts +37 -0
- package/src/logic/schemaErrorLookup.ts +54 -0
- package/src/logic/shouldRenderFormState.ts +25 -0
- package/src/logic/shouldSubscribeByName.ts +18 -0
- package/src/logic/skipValidation.ts +21 -0
- package/src/logic/unsetEmptyArray.ts +6 -0
- package/src/logic/updateFieldArrayRootError.ts +17 -0
- package/src/logic/validateField.ts +273 -0
- package/src/types/controller.ts +98 -0
- package/src/types/errors.ts +56 -0
- package/src/types/events.ts +26 -0
- package/src/types/fieldArray.ts +288 -0
- package/src/types/fields.ts +46 -0
- package/src/types/form.ts +961 -0
- package/src/types/index.ts +12 -0
- package/src/types/path/common.ts +391 -0
- package/src/types/path/eager.ts +224 -0
- package/src/types/path/index.ts +16 -0
- package/src/types/resolvers.ts +38 -0
- package/src/types/utils.ts +125 -0
- package/src/types/validator.ts +94 -0
- package/src/types/watch.ts +66 -0
- package/src/useController.ts +268 -0
- package/src/useFieldArray.ts +490 -0
- package/src/useForm.ts +182 -0
- package/src/useFormContext.ts +47 -0
- package/src/useFormControlContext.ts +25 -0
- package/src/useFormState.ts +86 -0
- package/src/useIsomorphicLayoutEffect.ts +6 -0
- package/src/useWatch.ts +356 -0
- package/src/utils/append.ts +4 -0
- package/src/utils/cloneObject.ts +32 -0
- package/src/utils/compact.ts +2 -0
- package/src/utils/convertToArrayPayload.ts +2 -0
- package/src/utils/createSubject.ts +48 -0
- package/src/utils/deepEqual.ts +81 -0
- package/src/utils/deepMerge.ts +27 -0
- package/src/utils/extractFormValues.ts +28 -0
- package/src/utils/fillEmptyArray.ts +3 -0
- package/src/utils/flatten.ts +23 -0
- package/src/utils/get.ts +29 -0
- package/src/utils/index.ts +3 -0
- package/src/utils/insert.ts +8 -0
- package/src/utils/isBoolean.ts +2 -0
- package/src/utils/isCheckBoxInput.ts +4 -0
- package/src/utils/isDateObject.ts +2 -0
- package/src/utils/isEmptyObject.ts +7 -0
- package/src/utils/isFileInput.ts +4 -0
- package/src/utils/isFunction.ts +2 -0
- package/src/utils/isHTMLElement.ts +13 -0
- package/src/utils/isKey.ts +4 -0
- package/src/utils/isMultipleSelect.ts +5 -0
- package/src/utils/isNullOrUndefined.ts +2 -0
- package/src/utils/isObject.ts +8 -0
- package/src/utils/isPlainObject.ts +8 -0
- package/src/utils/isPrimitive.ts +8 -0
- package/src/utils/isRadioInput.ts +4 -0
- package/src/utils/isRadioOrCheckbox.ts +8 -0
- package/src/utils/isRegex.ts +2 -0
- package/src/utils/isString.ts +2 -0
- package/src/utils/isUndefined.ts +2 -0
- package/src/utils/isWeb.ts +4 -0
- package/src/utils/live.ts +6 -0
- package/src/utils/move.ts +15 -0
- package/src/utils/noop.ts +2 -0
- package/src/utils/objectHasFunction.ts +11 -0
- package/src/utils/prepend.ts +7 -0
- package/src/utils/remove.ts +24 -0
- package/src/utils/set.ts +36 -0
- package/src/utils/sleep.ts +2 -0
- package/src/utils/stringToPath.ts +4 -0
- package/src/utils/swap.ts +4 -0
- package/src/utils/unset.ts +65 -0
- package/src/utils/update.ts +5 -0
- package/src/watch.tsrx +7 -0
- package/src/watch.tsrx.d.ts +42 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Dominic Gannaway
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# @octanejs/hook-form
|
|
2
|
+
|
|
3
|
+
React Hook Form for the [octane](https://github.com/octanejs/octane) renderer —
|
|
4
|
+
the complete react-hook-form 7.81.0 source ported onto octane's hooks:
|
|
5
|
+
performant, flexible forms with native-event validation, field arrays, schema
|
|
6
|
+
resolvers, `Controller`/`FormProvider`, and SSR.
|
|
7
|
+
|
|
8
|
+
```tsx
|
|
9
|
+
import { useForm } from '@octanejs/hook-form';
|
|
10
|
+
|
|
11
|
+
export function App() @{
|
|
12
|
+
const {
|
|
13
|
+
register,
|
|
14
|
+
handleSubmit,
|
|
15
|
+
formState: { errors },
|
|
16
|
+
} = useForm({ mode: 'onChange' });
|
|
17
|
+
|
|
18
|
+
<form onSubmit={handleSubmit((data) => console.log(data))}>
|
|
19
|
+
<input {...register('email', { required: 'required' })} />
|
|
20
|
+
@if (errors.email) {
|
|
21
|
+
<span role="alert">{errors.email.message as string}</span>
|
|
22
|
+
}
|
|
23
|
+
<button>{'Submit'}</button>
|
|
24
|
+
</form>
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## The one API difference: `onInput`
|
|
29
|
+
|
|
30
|
+
Octane has **native, delegated events** — no synthetic event layer. The
|
|
31
|
+
per-keystroke text event on the platform is `input` (native `change` fires on
|
|
32
|
+
blur/commit), so the handler upstream calls `onChange` is exposed as
|
|
33
|
+
**`onInput`**:
|
|
34
|
+
|
|
35
|
+
- `register(name)` returns `{ name, ref, onInput, onBlur }` (upstream:
|
|
36
|
+
`onChange`). `{...register('x')}` spreads work unchanged.
|
|
37
|
+
- `useController`'s `field` is `{ value, name, ref, onInput, onBlur, … }`.
|
|
38
|
+
`field.onInput` doubles as the programmatic setter (accepts an event or a
|
|
39
|
+
raw value), exactly like upstream's `field.onChange`.
|
|
40
|
+
- `mode: 'onChange'` / `reValidateMode: 'onChange'` option VALUES are
|
|
41
|
+
unchanged — validation still runs per keystroke (driven by native `input`).
|
|
42
|
+
- Register OPTIONS keep their upstream names: `register('x', { onChange, onBlur })`
|
|
43
|
+
callbacks are invoked as before.
|
|
44
|
+
|
|
45
|
+
Everything else — validation modes, `formState` proxy subscriptions,
|
|
46
|
+
`useFieldArray`, `useWatch`/`Watch`, `FormStateSubscribe`, `reset`/`setValue`/
|
|
47
|
+
`trigger` semantics, SSR via `octane/server` — matches upstream behavior; the
|
|
48
|
+
port runs react-hook-form's own ~1,200-test suite (see
|
|
49
|
+
`packages/hook-form/tests/`) plus differential tests asserting byte-identical
|
|
50
|
+
DOM against the real react-hook-form.
|
|
51
|
+
|
|
52
|
+
## License
|
|
53
|
+
|
|
54
|
+
MIT — contains source derived from
|
|
55
|
+
[react-hook-form](https://github.com/react-hook-form/react-hook-form)
|
|
56
|
+
(MIT, © Beier (Bill) Luo), adapted for octane.
|
|
57
|
+
|
|
58
|
+
## Status
|
|
59
|
+
|
|
60
|
+
Current scope, known divergences, and verification status are tracked in the
|
|
61
|
+
generated [bindings status table](../../docs/bindings-status.md), sourced from
|
|
62
|
+
this package's [`status.json`](./status.json).
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@octanejs/hook-form",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"description": "React Hook Form for the octane renderer — the full react-hook-form 7.81.0 source ported onto octane's hooks: performant, flexible forms with native-event validation (onInput), field arrays, and schema resolvers.",
|
|
7
|
+
"author": {
|
|
8
|
+
"name": "Dominic Gannaway",
|
|
9
|
+
"email": "dg@domgan.com"
|
|
10
|
+
},
|
|
11
|
+
"publishConfig": {
|
|
12
|
+
"access": "public"
|
|
13
|
+
},
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "git+https://github.com/octanejs/octane.git",
|
|
17
|
+
"directory": "packages/hook-form"
|
|
18
|
+
},
|
|
19
|
+
"main": "src/index.ts",
|
|
20
|
+
"module": "src/index.ts",
|
|
21
|
+
"types": "src/index.ts",
|
|
22
|
+
"files": [
|
|
23
|
+
"src",
|
|
24
|
+
"README.md"
|
|
25
|
+
],
|
|
26
|
+
"exports": {
|
|
27
|
+
".": "./src/index.ts"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"octane": "0.1.3"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@testing-library/jest-dom": "^6.9.1",
|
|
34
|
+
"@tsrx/react": "^0.2.37",
|
|
35
|
+
"esbuild": "^0.28.1",
|
|
36
|
+
"react": "^19.2.0",
|
|
37
|
+
"react-dom": "^19.2.0",
|
|
38
|
+
"react-hook-form": "7.81.0",
|
|
39
|
+
"vitest": "^4.1.9",
|
|
40
|
+
"zod": "^3.25.76",
|
|
41
|
+
"@octanejs/testing-library": "0.1.0"
|
|
42
|
+
},
|
|
43
|
+
"scripts": {
|
|
44
|
+
"test": "vitest run"
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// Vendored from react-hook-form@7.81.0 src/useFormContext.tsx (octane port) —
|
|
2
|
+
// the FormProvider component half of upstream's file. Propagates the useForm
|
|
3
|
+
// methods to all children via context; the memoized value keeps context
|
|
4
|
+
// identity stable across parent re-renders (same deps as upstream).
|
|
5
|
+
import { useMemo } from 'octane';
|
|
6
|
+
import { HookFormContext, HookFormControlContext } from './useFormControlContext.ts';
|
|
7
|
+
|
|
8
|
+
export function FormProvider(props) @{
|
|
9
|
+
const {
|
|
10
|
+
children,
|
|
11
|
+
watch,
|
|
12
|
+
getValues,
|
|
13
|
+
getFieldState,
|
|
14
|
+
setError,
|
|
15
|
+
clearErrors,
|
|
16
|
+
setValue,
|
|
17
|
+
setValues,
|
|
18
|
+
trigger,
|
|
19
|
+
formState,
|
|
20
|
+
resetField,
|
|
21
|
+
reset,
|
|
22
|
+
handleSubmit,
|
|
23
|
+
unregister,
|
|
24
|
+
control,
|
|
25
|
+
register,
|
|
26
|
+
setFocus,
|
|
27
|
+
subscribe,
|
|
28
|
+
} = props;
|
|
29
|
+
|
|
30
|
+
const memoizedValue = useMemo(
|
|
31
|
+
() => ({
|
|
32
|
+
watch,
|
|
33
|
+
getValues,
|
|
34
|
+
getFieldState,
|
|
35
|
+
setError,
|
|
36
|
+
clearErrors,
|
|
37
|
+
setValue,
|
|
38
|
+
setValues,
|
|
39
|
+
trigger,
|
|
40
|
+
formState,
|
|
41
|
+
resetField,
|
|
42
|
+
reset,
|
|
43
|
+
handleSubmit,
|
|
44
|
+
unregister,
|
|
45
|
+
control,
|
|
46
|
+
register,
|
|
47
|
+
setFocus,
|
|
48
|
+
subscribe,
|
|
49
|
+
}),
|
|
50
|
+
[
|
|
51
|
+
clearErrors,
|
|
52
|
+
control,
|
|
53
|
+
formState,
|
|
54
|
+
getFieldState,
|
|
55
|
+
getValues,
|
|
56
|
+
handleSubmit,
|
|
57
|
+
register,
|
|
58
|
+
reset,
|
|
59
|
+
resetField,
|
|
60
|
+
setError,
|
|
61
|
+
setFocus,
|
|
62
|
+
setValue,
|
|
63
|
+
setValues,
|
|
64
|
+
subscribe,
|
|
65
|
+
trigger,
|
|
66
|
+
unregister,
|
|
67
|
+
watch,
|
|
68
|
+
],
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
<HookFormContext.Provider value={memoizedValue}>
|
|
72
|
+
<HookFormControlContext.Provider
|
|
73
|
+
value={memoizedValue.control}
|
|
74
|
+
>{children}</HookFormControlContext.Provider>
|
|
75
|
+
</HookFormContext.Provider>
|
|
76
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// Type declaration for the .tsrx component (resolved by relative path).
|
|
2
|
+
import type { FieldValues, FormProviderProps } from './types';
|
|
3
|
+
|
|
4
|
+
export declare const FormProvider: <
|
|
5
|
+
TFieldValues extends FieldValues,
|
|
6
|
+
TContext = any,
|
|
7
|
+
TTransformedValues = TFieldValues,
|
|
8
|
+
>(
|
|
9
|
+
props: FormProviderProps<TFieldValues, TContext, TTransformedValues>,
|
|
10
|
+
) => unknown;
|
package/src/constants.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// Vendored from react-hook-form@7.81.0 src/constants.ts (octane port).
|
|
2
|
+
export const EVENTS = {
|
|
3
|
+
BLUR: 'blur',
|
|
4
|
+
FOCUS_OUT: 'focusout',
|
|
5
|
+
CHANGE: 'change',
|
|
6
|
+
SUBMIT: 'submit',
|
|
7
|
+
TRIGGER: 'trigger',
|
|
8
|
+
VALID: 'valid',
|
|
9
|
+
} as const;
|
|
10
|
+
|
|
11
|
+
export const VALIDATION_MODE = {
|
|
12
|
+
onBlur: 'onBlur',
|
|
13
|
+
onChange: 'onChange',
|
|
14
|
+
onSubmit: 'onSubmit',
|
|
15
|
+
onTouched: 'onTouched',
|
|
16
|
+
all: 'all',
|
|
17
|
+
} as const;
|
|
18
|
+
|
|
19
|
+
export const INPUT_VALIDATION_RULES = {
|
|
20
|
+
max: 'max',
|
|
21
|
+
min: 'min',
|
|
22
|
+
maxLength: 'maxLength',
|
|
23
|
+
minLength: 'minLength',
|
|
24
|
+
pattern: 'pattern',
|
|
25
|
+
required: 'required',
|
|
26
|
+
validate: 'validate',
|
|
27
|
+
} as const;
|
|
28
|
+
|
|
29
|
+
export const ROOT_ERROR_TYPE = 'root';
|
|
30
|
+
|
|
31
|
+
export const PROTOTYPE_KEYWORDS = ['__proto__', 'constructor', 'prototype'];
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// Vendored from react-hook-form@7.81.0 src/controller.tsx (octane port).
|
|
2
|
+
// Component based on `useController` to work with controlled components.
|
|
3
|
+
// Authored as .tsrx (full compile) — a component in binding src must have its
|
|
4
|
+
// custom-hook call slot-wrapped by the compiler; the plain-.ts slotting pass
|
|
5
|
+
// only slots BASE octane hooks (see compiler/slot-hooks.js).
|
|
6
|
+
import { useController } from './useController.ts';
|
|
7
|
+
|
|
8
|
+
export const Controller = (props) => props.render(useController(props));
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// Type declaration for the .tsrx component (resolved by relative path).
|
|
2
|
+
import type { ControllerProps, FieldPath, FieldValues } from './types';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Component based on `useController` hook to work with controlled component.
|
|
6
|
+
*
|
|
7
|
+
* @remarks
|
|
8
|
+
* [API](https://react-hook-form.com/docs/usecontroller/controller) • [Demo](https://codesandbox.io/s/react-hook-form-v6-controller-ts-jwyzw) • [Video](https://www.youtube.com/watch?v=N2UNk_UCVyA)
|
|
9
|
+
*
|
|
10
|
+
* @param props - the path name to the form field value, and validation rules.
|
|
11
|
+
*
|
|
12
|
+
* @returns provide field handler functions, field and form state.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```tsx
|
|
16
|
+
* function App() {
|
|
17
|
+
* const { control } = useForm<FormValues>({
|
|
18
|
+
* defaultValues: {
|
|
19
|
+
* test: ""
|
|
20
|
+
* }
|
|
21
|
+
* });
|
|
22
|
+
*
|
|
23
|
+
* return (
|
|
24
|
+
* <form>
|
|
25
|
+
* <Controller
|
|
26
|
+
* control={control}
|
|
27
|
+
* name="test"
|
|
28
|
+
* render={({ field: { onInput, onBlur, value, ref }, formState, fieldState }) => (
|
|
29
|
+
* <>
|
|
30
|
+
* <input
|
|
31
|
+
* onInput={onInput} // send value to hook form
|
|
32
|
+
* onBlur={onBlur} // notify when input is touched
|
|
33
|
+
* value={value} // return updated value
|
|
34
|
+
* ref={ref} // set ref for focus management
|
|
35
|
+
* />
|
|
36
|
+
* <p>{formState.isSubmitted ? "submitted" : ""}</p>
|
|
37
|
+
* <p>{fieldState.isTouched ? "touched" : ""}</p>
|
|
38
|
+
* </>
|
|
39
|
+
* )}
|
|
40
|
+
* />
|
|
41
|
+
* </form>
|
|
42
|
+
* );
|
|
43
|
+
* }
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
export declare const Controller: <
|
|
47
|
+
TFieldValues extends FieldValues = FieldValues,
|
|
48
|
+
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
|
49
|
+
TTransformedValues = TFieldValues,
|
|
50
|
+
>(
|
|
51
|
+
props: ControllerProps<TFieldValues, TName, TTransformedValues>,
|
|
52
|
+
) => unknown;
|
package/src/form.tsrx
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
// Vendored from react-hook-form@7.81.0 src/form.tsx (octane port).
|
|
2
|
+
// Form component to manage submission — renders a real <form> (or the headless
|
|
3
|
+
// render prop) and posts via fetch. Same submission/error semantics as
|
|
4
|
+
// upstream; the submit handler receives the NATIVE submit event.
|
|
5
|
+
import { useState, useCallback, useEffect } from 'octane';
|
|
6
|
+
import { flatten } from './utils/flatten.ts';
|
|
7
|
+
import { useFormContext } from './useFormContext.ts';
|
|
8
|
+
|
|
9
|
+
const POST_REQUEST = 'post';
|
|
10
|
+
|
|
11
|
+
export function Form(props) {
|
|
12
|
+
const methods = useFormContext();
|
|
13
|
+
const [mounted, setMounted] = useState(false);
|
|
14
|
+
const {
|
|
15
|
+
control = methods.control,
|
|
16
|
+
onSubmit,
|
|
17
|
+
children,
|
|
18
|
+
action,
|
|
19
|
+
method = POST_REQUEST,
|
|
20
|
+
headers,
|
|
21
|
+
encType,
|
|
22
|
+
onError,
|
|
23
|
+
render,
|
|
24
|
+
onSuccess,
|
|
25
|
+
validateStatus,
|
|
26
|
+
...rest
|
|
27
|
+
} = props;
|
|
28
|
+
|
|
29
|
+
const submit = useCallback(async (event) => {
|
|
30
|
+
let hasError = false;
|
|
31
|
+
let type = '';
|
|
32
|
+
|
|
33
|
+
await control.handleSubmit(async (data) => {
|
|
34
|
+
const formData = new FormData();
|
|
35
|
+
let formDataJson = '';
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
formDataJson = JSON.stringify(data);
|
|
39
|
+
} catch {
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const flattenFormValues = flatten(data);
|
|
43
|
+
|
|
44
|
+
for (const key in flattenFormValues) {
|
|
45
|
+
formData.append(key, flattenFormValues[key]);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (onSubmit) {
|
|
49
|
+
await onSubmit({
|
|
50
|
+
data,
|
|
51
|
+
event,
|
|
52
|
+
method,
|
|
53
|
+
formData,
|
|
54
|
+
formDataJson,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (action) {
|
|
59
|
+
try {
|
|
60
|
+
const shouldStringifySubmissionData = [
|
|
61
|
+
headers && headers['Content-Type'],
|
|
62
|
+
encType,
|
|
63
|
+
].some((value) => value && value.includes('json'));
|
|
64
|
+
|
|
65
|
+
const response = await fetch(String(action), {
|
|
66
|
+
method,
|
|
67
|
+
headers: {
|
|
68
|
+
...headers,
|
|
69
|
+
...(encType && encType !== 'multipart/form-data' ? { 'Content-Type': encType } : {}),
|
|
70
|
+
},
|
|
71
|
+
body: shouldStringifySubmissionData ? formDataJson : formData,
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
if (
|
|
75
|
+
response &&
|
|
76
|
+
(validateStatus
|
|
77
|
+
? !validateStatus(response.status)
|
|
78
|
+
: response.status < 200 || response.status >= 300)
|
|
79
|
+
) {
|
|
80
|
+
hasError = true;
|
|
81
|
+
onError && onError({ response });
|
|
82
|
+
type = String(response.status);
|
|
83
|
+
} else {
|
|
84
|
+
onSuccess && onSuccess({ response });
|
|
85
|
+
}
|
|
86
|
+
} catch (error) {
|
|
87
|
+
hasError = true;
|
|
88
|
+
onError && onError({ error });
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
})(event);
|
|
92
|
+
|
|
93
|
+
if (hasError && control) {
|
|
94
|
+
control._subjects.state.next({
|
|
95
|
+
isSubmitSuccessful: false,
|
|
96
|
+
});
|
|
97
|
+
control.setError('root.server', {
|
|
98
|
+
type,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
}, [control, onSubmit, method, action, headers, encType, validateStatus, onError, onSuccess]);
|
|
102
|
+
|
|
103
|
+
useEffect(() => {
|
|
104
|
+
setMounted(true);
|
|
105
|
+
}, []);
|
|
106
|
+
|
|
107
|
+
if (render) {
|
|
108
|
+
return render({ submit });
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return <form
|
|
112
|
+
noValidate={mounted}
|
|
113
|
+
action={action}
|
|
114
|
+
method={method}
|
|
115
|
+
encType={encType}
|
|
116
|
+
onSubmit={submit}
|
|
117
|
+
{...rest}
|
|
118
|
+
>{children}</form>;
|
|
119
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// Type declaration for the .tsrx component (resolved by relative path).
|
|
2
|
+
import type { FieldValues, FormProps } from './types';
|
|
3
|
+
|
|
4
|
+
export declare function Form<TFieldValues extends FieldValues, TTransformedValues = TFieldValues>(
|
|
5
|
+
props: FormProps<TFieldValues, TTransformedValues>,
|
|
6
|
+
): unknown;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// Vendored from react-hook-form@7.81.0 src/formStateSubscribe.tsx (octane port).
|
|
2
|
+
// FormStateSubscribe component — render-prop subscription to form state.
|
|
3
|
+
// Authored as .tsrx (full compile) so the useFormState call is slot-wrapped
|
|
4
|
+
// (see controller.tsrx note).
|
|
5
|
+
import { useFormState } from './useFormState.ts';
|
|
6
|
+
|
|
7
|
+
export const FormStateSubscribe = (props) => {
|
|
8
|
+
const { control, disabled, exact, name, render } = props;
|
|
9
|
+
return render(useFormState({ control, name, disabled, exact }));
|
|
10
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// Type declaration for the .tsrx component (resolved by relative path).
|
|
2
|
+
import type { FieldValues, UseFormStateProps, UseFormStateReturn } from './types';
|
|
3
|
+
|
|
4
|
+
export type FormStateSubscribeProps<
|
|
5
|
+
TFieldValues extends FieldValues,
|
|
6
|
+
TTransformedValues = TFieldValues,
|
|
7
|
+
> = UseFormStateProps<TFieldValues, TTransformedValues> & {
|
|
8
|
+
render: (values: UseFormStateReturn<TFieldValues>) => unknown;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export declare const FormStateSubscribe: <
|
|
12
|
+
TFieldValues extends FieldValues,
|
|
13
|
+
TTransformedValues = TFieldValues,
|
|
14
|
+
>(
|
|
15
|
+
props: FormStateSubscribeProps<TFieldValues, TTransformedValues>,
|
|
16
|
+
) => unknown;
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// Vendored from react-hook-form@7.81.0 src/index.ts (octane port).
|
|
2
|
+
// Same public surface as upstream (pinned by tests/conformance/exports.test.ts);
|
|
3
|
+
// upstream's `./form` and the FormProvider half of `./useFormContext` are .tsrx
|
|
4
|
+
// components here. index.react-server.ts is not ported (octane has no server
|
|
5
|
+
// components).
|
|
6
|
+
export { Controller } from './controller.tsrx';
|
|
7
|
+
export { Form } from './form.tsrx';
|
|
8
|
+
export { FormStateSubscribe } from './formStateSubscribe.tsrx';
|
|
9
|
+
export type { FormStateSubscribeProps } from './formStateSubscribe.tsrx';
|
|
10
|
+
export * from './logic';
|
|
11
|
+
export * from './types';
|
|
12
|
+
export * from './useController';
|
|
13
|
+
export * from './useFieldArray';
|
|
14
|
+
export * from './useForm';
|
|
15
|
+
export * from './useFormContext';
|
|
16
|
+
export * from './useFormState';
|
|
17
|
+
export * from './useWatch';
|
|
18
|
+
export * from './utils';
|
|
19
|
+
export { Watch } from './watch.tsrx';
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// Vendored from react-hook-form@7.81.0 src/logic/appendErrors.ts (octane port).
|
|
2
|
+
import type { InternalFieldErrors, InternalFieldName, ValidateResult } from '../types';
|
|
3
|
+
|
|
4
|
+
export default (
|
|
5
|
+
name: InternalFieldName,
|
|
6
|
+
validateAllFieldCriteria: boolean,
|
|
7
|
+
errors: InternalFieldErrors,
|
|
8
|
+
type: string,
|
|
9
|
+
message: ValidateResult,
|
|
10
|
+
) =>
|
|
11
|
+
validateAllFieldCriteria
|
|
12
|
+
? {
|
|
13
|
+
...errors[name],
|
|
14
|
+
types: {
|
|
15
|
+
...(errors[name] && errors[name]!.types ? errors[name]!.types : {}),
|
|
16
|
+
[type]: message || true,
|
|
17
|
+
},
|
|
18
|
+
}
|
|
19
|
+
: {};
|