@octanejs/hook-form 0.1.20 → 0.1.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -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
- Everything else — validation modes, `formState` proxy subscriptions,
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
- 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.
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,6 +1,6 @@
1
1
  {
2
2
  "name": "@octanejs/hook-form",
3
- "version": "0.1.20",
3
+ "version": "0.1.21",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "engines": {
@@ -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.23"
34
+ "octane": "0.1.24"
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.20",
45
- "octane": "0.1.23"
54
+ "@octanejs/testing-library": "0.1.21",
55
+ "octane": "0.1.24"
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
  }
@@ -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(props) @{
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
  }
@@ -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 = (props) => props.render(useController(props));
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(props) {
12
- const methods = useFormContext();
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 const FormStateSubscribe = (props) => {
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 = (props) => props.render(useWatch({ name: props.names, ...props }));
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
+ );