@octanejs/hook-form 0.1.20 → 0.1.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -4
- package/UPSTREAM.md +103 -0
- package/package.json +21 -7
- package/src/FormProvider.tsrx +11 -3
- package/src/controller.tsrx +8 -1
- package/src/form.tsrx +8 -5
- package/src/formStateSubscribe.tsrx +14 -1
- package/src/watch.tsrx +19 -1
package/README.md
CHANGED
|
@@ -42,12 +42,26 @@ blur/commit), so the handler upstream calls `onChange` is exposed as
|
|
|
42
42
|
- Register OPTIONS keep their upstream names: `register('x', { onChange, onBlur })`
|
|
43
43
|
callbacks are invoked as before.
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
The remaining adapted behavior — validation modes, `formState` proxy subscriptions,
|
|
46
46
|
`useFieldArray`, `useWatch`/`Watch`, `FormStateSubscribe`, `reset`/`setValue`/
|
|
47
47
|
`trigger` semantics, SSR via `octane/server` — matches upstream behavior; the
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
DOM
|
|
48
|
+
automated parity checks run all 1,193 tests and eight snapshots unchanged
|
|
49
|
+
against the pinned React package as a pristine baseline. The Octane port
|
|
50
|
+
separately runs its byte-locked adapted DOM and server suites without title
|
|
51
|
+
filters. Their manifests record 1,187 collected entries representing 1,178
|
|
52
|
+
unique file/full-name identities and require every entry to execute and pass.
|
|
53
|
+
The nine duplicate entries are repeated titles within the DOM inventory; the
|
|
54
|
+
server inventory is disjoint. The structured divergence ledger in
|
|
55
|
+
[`audit/react-parity.json`](./audit/react-parity.json) records the native-event,
|
|
56
|
+
scheduling, render-bailout, and test-flush differences, including consumer
|
|
57
|
+
impact and migration guidance. Differential tests also assert byte-identical
|
|
58
|
+
DOM against the real react-hook-form. See
|
|
59
|
+
[`UPSTREAM.md`](./UPSTREAM.md) for the pin,
|
|
60
|
+
source inventory, export crosswalk, and test dispositions. The root Vitest
|
|
61
|
+
project declares upstream and differential files as `react-parity` execution
|
|
62
|
+
group ownership; the generic sharded config derives the remaining local
|
|
63
|
+
conformance suite according to the
|
|
64
|
+
[React parity test-execution contract](../../docs/react-parity-testing.md).
|
|
51
65
|
|
|
52
66
|
## License
|
|
53
67
|
|
package/UPSTREAM.md
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# react-hook-form upstream contract
|
|
2
|
+
|
|
3
|
+
## Pin and source boundary
|
|
4
|
+
|
|
5
|
+
| Field | Value |
|
|
6
|
+
|---|---|
|
|
7
|
+
| Package | `react-hook-form` |
|
|
8
|
+
| Version | `7.81.0` |
|
|
9
|
+
| Canonical tag commit | `46b217e034dd92f7aa3cb3a478815556b416b299` |
|
|
10
|
+
| Supported upstream range | exactly `7.81.0` |
|
|
11
|
+
| React oracle | `19.2.7` |
|
|
12
|
+
| Canonical archive SHA-256 | `2d49ffe1a26427eb579201d574fa6a76903f8a15bae5b0bab20c434273ebc8bd` |
|
|
13
|
+
| License | MIT, © Beier (Bill) Luo |
|
|
14
|
+
|
|
15
|
+
The npm artifact publishes compiled `dist/` output and declarations only. The
|
|
16
|
+
byte-exact source, original tests, snapshots, Jest configuration, package
|
|
17
|
+
metadata, and license therefore come from the canonical repository at the tag
|
|
18
|
+
commit above. They live under `upstream/`, are excluded from the published
|
|
19
|
+
package, and are locked file-by-file by `upstream/SHA256SUMS`.
|
|
20
|
+
|
|
21
|
+
Run `pnpm --dir packages/hook-form upstream:verify` to check the vendored bytes
|
|
22
|
+
and the one-for-one adapted-suite inventory. Run
|
|
23
|
+
`pnpm --dir packages/hook-form test:upstream` to execute the original React/Jest
|
|
24
|
+
suite unchanged.
|
|
25
|
+
|
|
26
|
+
## Runtime export crosswalk
|
|
27
|
+
|
|
28
|
+
| Upstream export | Octane disposition | Evidence |
|
|
29
|
+
|---|---|---|
|
|
30
|
+
| `Controller` | Ported to `src/controller.tsrx` | `tests/upstream/controller.test.tsx`, `tests/upstream/useController.test.tsx` |
|
|
31
|
+
| `Form` | Ported to `src/form.tsrx` | `tests/upstream/form.test.tsx` |
|
|
32
|
+
| `FormProvider` | Ported to `src/FormProvider.tsrx` | `tests/upstream/useFormContext.test.tsx` |
|
|
33
|
+
| `FormStateSubscribe` | Ported to `src/formStateSubscribe.tsrx` | `tests/upstream/formStateSubscribe.test.tsx` |
|
|
34
|
+
| `Watch` | Ported to `src/watch.tsrx` | `tests/upstream/watch.test.tsx` |
|
|
35
|
+
| `appendErrors` | Reused framework-neutral logic | `tests/upstream/logic/appendErrors.test.ts` |
|
|
36
|
+
| `createFormControl` | Reused framework-neutral logic | `tests/upstream/logic/createFormControl.test.ts` |
|
|
37
|
+
| `get` | Reused framework-neutral utility | `tests/upstream/utils/get.test.ts` |
|
|
38
|
+
| `set` | Reused framework-neutral utility | `tests/upstream/utils/set.test.ts` |
|
|
39
|
+
| `useController` | Ported to Octane hooks; `field.onInput` is the documented event divergence | `tests/upstream/useController.test.tsx` |
|
|
40
|
+
| `useFieldArray` | Ported to Octane hooks | `tests/upstream/useFieldArray.test.tsx` and `tests/upstream/useFieldArray/` |
|
|
41
|
+
| `useForm` | Ported to Octane hooks | `tests/upstream/useForm.test.tsx` and `tests/upstream/useForm/` |
|
|
42
|
+
| `useFormContext` | Ported to Octane context | `tests/upstream/useFormContext.test.tsx` |
|
|
43
|
+
| `useFormState` | Ported to Octane hooks | `tests/upstream/useFormState.test.tsx` |
|
|
44
|
+
| `useWatch` | Ported to Octane hooks | `tests/upstream/useWatch.test.tsx` |
|
|
45
|
+
| `types/controller` exports | Ported; event-facing controller fields expose `onInput` | `typetests/` and package typecheck |
|
|
46
|
+
| `types/errors` exports | Ported at the same module path | `typetests/` and package typecheck |
|
|
47
|
+
| `types/events` exports | Ported; native change events map to input events | `typetests/` and package typecheck |
|
|
48
|
+
| `types/fieldArray` exports | Ported at the same module path | `typetests/` and package typecheck |
|
|
49
|
+
| `types/fields` exports | Ported; event-facing fields expose `onInput` | `typetests/` and package typecheck |
|
|
50
|
+
| `types/form` exports | Ported at the same module path | `typetests/` and package typecheck |
|
|
51
|
+
| `types/path` exports | Ported at the same module path | `typetests/` and package typecheck |
|
|
52
|
+
| `types/resolvers` exports | Ported at the same module path | `typetests/` and package typecheck |
|
|
53
|
+
| `types/utils` exports | Ported at the same module path | `typetests/` and package typecheck |
|
|
54
|
+
| `types/validator` exports | Ported at the same module path | `typetests/` and package typecheck |
|
|
55
|
+
| `types/watch` exports | Ported at the same module path | `typetests/` and package typecheck |
|
|
56
|
+
|
|
57
|
+
`tests/conformance/exports.test.ts` compares the runtime namespace to the real
|
|
58
|
+
package and fails for either a missing export or an accidental extra.
|
|
59
|
+
`index.react-server.ts` is not a public runtime export in Octane because Octane
|
|
60
|
+
does not implement React Server Components; ordinary SSR is supported and
|
|
61
|
+
covered by every adapted `*.server.test.tsx` counterpart.
|
|
62
|
+
|
|
63
|
+
## Test-suite disposition
|
|
64
|
+
|
|
65
|
+
The tagged repository contains 119 test artifacts: 115 test files plus four
|
|
66
|
+
snapshots. Every relative path under upstream `src/__tests__/` has an adapted
|
|
67
|
+
counterpart under `tests/upstream/`. The verifier extracts registrations from
|
|
68
|
+
both trees and currently accounts for 1,178 upstream registrations and 1,181
|
|
69
|
+
adapted registrations.
|
|
70
|
+
|
|
71
|
+
The unfiltered execution inventories are a separate collection-time
|
|
72
|
+
measurement. They contain 1,187 entries representing 1,178 unique
|
|
73
|
+
file/full-name identities. All nine duplicate entries are repeated titles
|
|
74
|
+
within the DOM inventory; the server inventory shares no file or test identity
|
|
75
|
+
with it. The manifest records these measurements structurally, and validation
|
|
76
|
+
recomputes them from the committed inventories.
|
|
77
|
+
|
|
78
|
+
Three upstream titles are deliberately mapped to their Octane equivalents:
|
|
79
|
+
|
|
80
|
+
- `field.onChange` → `field.onInput` in the controller promise case;
|
|
81
|
+
- the same native-event rename in the resolver subscriber case; and
|
|
82
|
+
- React's batched trigger notification case → Octane's documented commit-order
|
|
83
|
+
notification case.
|
|
84
|
+
|
|
85
|
+
The adapted suite adds three Octane regression cases for registered array-valued
|
|
86
|
+
dirty fields. These are explicit additions, not substitutes for upstream cases.
|
|
87
|
+
There are no skipped, todo, or expected-failure cases. Any removed/renamed test
|
|
88
|
+
artifact, unrecorded title change, missing extra, or vendored-byte drift fails
|
|
89
|
+
`upstream:verify`.
|
|
90
|
+
|
|
91
|
+
In the root Vitest project, `testExecution.group: react-parity` owns the adapted
|
|
92
|
+
upstream and differential test patterns. The ordinary sharded config derives
|
|
93
|
+
their complement, so the package-authored conformance cases still run without
|
|
94
|
+
repeating manifest-owned parity work. This follows the repository-wide
|
|
95
|
+
[React parity test-execution contract](../../docs/react-parity-testing.md); the
|
|
96
|
+
workflow does not enumerate this package.
|
|
97
|
+
|
|
98
|
+
The pristine lane runs all 1,193 tests and eight snapshots in the original Jest
|
|
99
|
+
suite against React. Its local Jest wrapper removes only the optional
|
|
100
|
+
`jest-preview` dashboard transforms/setup; test source, SWC transformation,
|
|
101
|
+
environments, assertions, and snapshots remain upstream-exact. The adapted and
|
|
102
|
+
differential lanes then verify the Octane port, including the native input-event
|
|
103
|
+
divergence.
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@octanejs/hook-form",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.22",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
7
|
-
"node": ">=22"
|
|
7
|
+
"node": ">=22.22.2"
|
|
8
8
|
},
|
|
9
9
|
"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.",
|
|
10
10
|
"author": {
|
|
@@ -24,27 +24,41 @@
|
|
|
24
24
|
"types": "src/index.ts",
|
|
25
25
|
"files": [
|
|
26
26
|
"src",
|
|
27
|
-
"README.md"
|
|
27
|
+
"README.md",
|
|
28
|
+
"UPSTREAM.md"
|
|
28
29
|
],
|
|
29
30
|
"exports": {
|
|
30
31
|
".": "./src/index.ts"
|
|
31
32
|
},
|
|
32
33
|
"peerDependencies": {
|
|
33
|
-
"octane": "0.1.
|
|
34
|
+
"octane": "0.1.25"
|
|
34
35
|
},
|
|
35
36
|
"devDependencies": {
|
|
37
|
+
"@swc/core": "1.15.41",
|
|
38
|
+
"@swc/jest": "0.2.39",
|
|
36
39
|
"@testing-library/jest-dom": "^6.9.1",
|
|
40
|
+
"@testing-library/react": "16.3.2",
|
|
41
|
+
"@types/react": "^19.2.17",
|
|
42
|
+
"@types/react-dom": "^19.2.3",
|
|
37
43
|
"@tsrx/react": "^0.2.56",
|
|
38
44
|
"esbuild": "^0.28.1",
|
|
45
|
+
"jest": "30.4.2",
|
|
46
|
+
"jest-environment-jsdom": "30.4.1",
|
|
47
|
+
"jest-fixed-jsdom": "0.0.10",
|
|
39
48
|
"react": "^19.2.7",
|
|
40
49
|
"react-dom": "^19.2.7",
|
|
41
50
|
"react-hook-form": "7.81.0",
|
|
42
51
|
"vitest": "^4.1.10",
|
|
52
|
+
"whatwg-fetch": "3.6.20",
|
|
43
53
|
"zod": "^3.25.76",
|
|
44
|
-
"@octanejs/testing-library": "0.1.
|
|
45
|
-
"octane": "0.1.
|
|
54
|
+
"@octanejs/testing-library": "0.1.22",
|
|
55
|
+
"octane": "0.1.25"
|
|
46
56
|
},
|
|
47
57
|
"scripts": {
|
|
48
|
-
"test": "vitest run"
|
|
58
|
+
"test": "pnpm upstream:verify && pnpm test:type-parity && vitest run --root ../.. --project hook-form && vitest run --root ../.. --project hook-form-differential && vitest run --root ../.. --project hook-form-server && vitest run --root ../.. --project hook-form-pristine",
|
|
59
|
+
"test:upstream": "jest --config tests/upstream-jest.config.cjs --rootDir upstream --runInBand --no-watchman",
|
|
60
|
+
"upstream:verify": "node ../../scripts/react-parity/hook-form-upstream.mjs",
|
|
61
|
+
"test:type-parity": "node ../../scripts/react-parity/hook-form-types.mjs",
|
|
62
|
+
"typecheck": "tsrx-tsc --noEmit -p tsconfig.json && tsrx-tsc --noEmit -p typetests/tsconfig.json"
|
|
49
63
|
}
|
|
50
64
|
}
|
package/src/FormProvider.tsrx
CHANGED
|
@@ -3,9 +3,14 @@
|
|
|
3
3
|
// methods to all children via context; the memoized value keeps context
|
|
4
4
|
// identity stable across parent re-renders (same deps as upstream).
|
|
5
5
|
import { useMemo } from 'octane';
|
|
6
|
+
import type { Control, FieldValues, FormProviderProps, UseFormReturn } from './types';
|
|
6
7
|
import { HookFormContext, HookFormControlContext } from './useFormControlContext.ts';
|
|
7
8
|
|
|
8
|
-
export function FormProvider
|
|
9
|
+
export function FormProvider<
|
|
10
|
+
TFieldValues extends FieldValues,
|
|
11
|
+
TContext = any,
|
|
12
|
+
TTransformedValues = TFieldValues,
|
|
13
|
+
>(props: FormProviderProps<TFieldValues, TContext, TTransformedValues>) @{
|
|
9
14
|
const {
|
|
10
15
|
children,
|
|
11
16
|
watch,
|
|
@@ -19,6 +24,7 @@ export function FormProvider(props) @{
|
|
|
19
24
|
formState,
|
|
20
25
|
resetField,
|
|
21
26
|
reset,
|
|
27
|
+
resetDefaultValues,
|
|
22
28
|
handleSubmit,
|
|
23
29
|
unregister,
|
|
24
30
|
control,
|
|
@@ -40,6 +46,7 @@ export function FormProvider(props) @{
|
|
|
40
46
|
formState,
|
|
41
47
|
resetField,
|
|
42
48
|
reset,
|
|
49
|
+
resetDefaultValues,
|
|
43
50
|
handleSubmit,
|
|
44
51
|
unregister,
|
|
45
52
|
control,
|
|
@@ -56,6 +63,7 @@ export function FormProvider(props) @{
|
|
|
56
63
|
handleSubmit,
|
|
57
64
|
register,
|
|
58
65
|
reset,
|
|
66
|
+
resetDefaultValues,
|
|
59
67
|
resetField,
|
|
60
68
|
setError,
|
|
61
69
|
setFocus,
|
|
@@ -68,9 +76,9 @@ export function FormProvider(props) @{
|
|
|
68
76
|
],
|
|
69
77
|
);
|
|
70
78
|
|
|
71
|
-
<HookFormContext.Provider value={memoizedValue}>
|
|
79
|
+
<HookFormContext.Provider value={memoizedValue as unknown as UseFormReturn}>
|
|
72
80
|
<HookFormControlContext.Provider
|
|
73
|
-
value={memoizedValue.control}
|
|
81
|
+
value={memoizedValue.control as Control}
|
|
74
82
|
>{children}</HookFormControlContext.Provider>
|
|
75
83
|
</HookFormContext.Provider>
|
|
76
84
|
}
|
package/src/controller.tsrx
CHANGED
|
@@ -4,5 +4,12 @@
|
|
|
4
4
|
// custom-hook call slot-wrapped by the compiler; the plain-.ts slotting pass
|
|
5
5
|
// only slots BASE octane hooks (see compiler/slot-hooks.js).
|
|
6
6
|
import { useController } from './useController.ts';
|
|
7
|
+
import type { ControllerProps, FieldPath, FieldValues } from './types';
|
|
7
8
|
|
|
8
|
-
export const Controller =
|
|
9
|
+
export const Controller = <
|
|
10
|
+
TFieldValues extends FieldValues = FieldValues,
|
|
11
|
+
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
|
12
|
+
TTransformedValues = TFieldValues,
|
|
13
|
+
>(props: ControllerProps<TFieldValues, TName, TTransformedValues>) => props.render(
|
|
14
|
+
useController<TFieldValues, TName, TTransformedValues>(props),
|
|
15
|
+
);
|
package/src/form.tsrx
CHANGED
|
@@ -5,11 +5,14 @@
|
|
|
5
5
|
import { useState, useCallback, useEffect } from 'octane';
|
|
6
6
|
import { flatten } from './utils/flatten.ts';
|
|
7
7
|
import { useFormContext } from './useFormContext.ts';
|
|
8
|
+
import type { FieldValues, FormProps } from './types';
|
|
8
9
|
|
|
9
10
|
const POST_REQUEST = 'post';
|
|
10
11
|
|
|
11
|
-
export function Form(
|
|
12
|
-
|
|
12
|
+
export function Form<TFieldValues extends FieldValues, TTransformedValues = TFieldValues>(
|
|
13
|
+
props: FormProps<TFieldValues, TTransformedValues>,
|
|
14
|
+
) {
|
|
15
|
+
const methods = useFormContext<TFieldValues, any, TTransformedValues>();
|
|
13
16
|
const [mounted, setMounted] = useState(false);
|
|
14
17
|
const {
|
|
15
18
|
control = methods.control,
|
|
@@ -39,7 +42,7 @@ export function Form(props) {
|
|
|
39
42
|
} catch {
|
|
40
43
|
}
|
|
41
44
|
|
|
42
|
-
const flattenFormValues = flatten(data);
|
|
45
|
+
const flattenFormValues = flatten(data as FieldValues);
|
|
43
46
|
|
|
44
47
|
for (const key in flattenFormValues) {
|
|
45
48
|
formData.append(key, flattenFormValues[key]);
|
|
@@ -47,7 +50,7 @@ export function Form(props) {
|
|
|
47
50
|
|
|
48
51
|
if (onSubmit) {
|
|
49
52
|
await onSubmit({
|
|
50
|
-
data,
|
|
53
|
+
data: data as TTransformedValues,
|
|
51
54
|
event,
|
|
52
55
|
method,
|
|
53
56
|
formData,
|
|
@@ -110,7 +113,7 @@ export function Form(props) {
|
|
|
110
113
|
|
|
111
114
|
return <form
|
|
112
115
|
noValidate={mounted}
|
|
113
|
-
action={action}
|
|
116
|
+
action={action as string | undefined}
|
|
114
117
|
method={method}
|
|
115
118
|
encType={encType}
|
|
116
119
|
onSubmit={submit}
|
|
@@ -3,8 +3,21 @@
|
|
|
3
3
|
// Authored as .tsrx (full compile) so the useFormState call is slot-wrapped
|
|
4
4
|
// (see controller.tsrx note).
|
|
5
5
|
import { useFormState } from './useFormState.ts';
|
|
6
|
+
import type { OctaneNode } from 'octane';
|
|
7
|
+
import type { FieldValues, UseFormStateProps, UseFormStateReturn } from './types';
|
|
6
8
|
|
|
7
|
-
export
|
|
9
|
+
export type FormStateSubscribeProps<
|
|
10
|
+
TFieldValues extends FieldValues,
|
|
11
|
+
TTransformedValues = TFieldValues,
|
|
12
|
+
> =
|
|
13
|
+
UseFormStateProps<TFieldValues, TTransformedValues> & {
|
|
14
|
+
render: (values: UseFormStateReturn<TFieldValues>) => OctaneNode;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const FormStateSubscribe = <
|
|
18
|
+
TFieldValues extends FieldValues,
|
|
19
|
+
TTransformedValues = TFieldValues,
|
|
20
|
+
>(props: FormStateSubscribeProps<TFieldValues, TTransformedValues>) => {
|
|
8
21
|
const { control, disabled, exact, name, render } = props;
|
|
9
22
|
return render(useFormState({ control, name, disabled, exact }));
|
|
10
23
|
};
|
package/src/watch.tsrx
CHANGED
|
@@ -3,5 +3,23 @@
|
|
|
3
3
|
// watched fields update. Authored as .tsrx (full compile) so the useWatch call
|
|
4
4
|
// is slot-wrapped (see controller.tsrx note).
|
|
5
5
|
import { useWatch } from './useWatch.ts';
|
|
6
|
+
import type { FieldPath, FieldValues, WatchProps, WatchRenderValue } from './types';
|
|
6
7
|
|
|
7
|
-
export const Watch =
|
|
8
|
+
export const Watch = <
|
|
9
|
+
TFieldValues extends FieldValues = FieldValues,
|
|
10
|
+
TFieldName extends | FieldPath<TFieldValues>
|
|
11
|
+
| readonly [FieldPath<TFieldValues>, ...FieldPath<TFieldValues>[]]
|
|
12
|
+
| FieldPath<TFieldValues>[]
|
|
13
|
+
| readonly FieldPath<TFieldValues>[]
|
|
14
|
+
| undefined = undefined,
|
|
15
|
+
TContext = any,
|
|
16
|
+
TTransformedValues = TFieldValues,
|
|
17
|
+
TComputeValue = undefined,
|
|
18
|
+
>(props: WatchProps<TFieldName, TFieldValues, TContext, TTransformedValues, TComputeValue>) =>
|
|
19
|
+
props.render(
|
|
20
|
+
useWatch({ name: props.names, ...props as any }) as WatchRenderValue<
|
|
21
|
+
TFieldName,
|
|
22
|
+
TFieldValues,
|
|
23
|
+
TComputeValue
|
|
24
|
+
>,
|
|
25
|
+
);
|