@octanejs/formisch 0.0.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.
Files changed (130) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +73 -0
  3. package/UPSTREAM.md +95 -0
  4. package/package.json +65 -0
  5. package/src/components/Field/Field.tsrx +22 -0
  6. package/src/components/Field/Field.tsrx.d.ts +17 -0
  7. package/src/components/Field/index.ts +1 -0
  8. package/src/components/FieldArray/FieldArray.tsrx +22 -0
  9. package/src/components/FieldArray/FieldArray.tsrx.d.ts +18 -0
  10. package/src/components/FieldArray/index.ts +1 -0
  11. package/src/components/Form/Form.tsrx +28 -0
  12. package/src/components/Form/Form.tsrx.d.ts +15 -0
  13. package/src/components/Form/index.ts +1 -0
  14. package/src/components/index.ts +3 -0
  15. package/src/core/array/copyItemState/copyItemState.ts +101 -0
  16. package/src/core/array/copyItemState/index.ts +1 -0
  17. package/src/core/array/index.ts +3 -0
  18. package/src/core/array/resetItemState/index.ts +1 -0
  19. package/src/core/array/resetItemState/resetItemState.ts +172 -0
  20. package/src/core/array/swapItemState/index.ts +1 -0
  21. package/src/core/array/swapItemState/swapItemState.ts +138 -0
  22. package/src/core/field/focusFieldElement/focusFieldElement.ts +32 -0
  23. package/src/core/field/focusFieldElement/index.ts +1 -0
  24. package/src/core/field/getDirtyFieldInput/getDirtyFieldInput.ts +66 -0
  25. package/src/core/field/getDirtyFieldInput/index.ts +1 -0
  26. package/src/core/field/getElementInput/getElementInput.ts +78 -0
  27. package/src/core/field/getElementInput/index.ts +1 -0
  28. package/src/core/field/getFieldBool/getFieldBool.ts +22 -0
  29. package/src/core/field/getFieldBool/index.ts +1 -0
  30. package/src/core/field/getFieldInput/getFieldInput.ts +52 -0
  31. package/src/core/field/getFieldInput/index.ts +1 -0
  32. package/src/core/field/getFieldStore/getFieldStore.ts +34 -0
  33. package/src/core/field/getFieldStore/index.ts +1 -0
  34. package/src/core/field/index.ts +11 -0
  35. package/src/core/field/initializeFieldStore/index.ts +1 -0
  36. package/src/core/field/initializeFieldStore/initializeFieldStore.ts +325 -0
  37. package/src/core/field/setFieldBool/index.ts +1 -0
  38. package/src/core/field/setFieldBool/setFieldBool.ts +29 -0
  39. package/src/core/field/setFieldInput/index.ts +1 -0
  40. package/src/core/field/setFieldInput/setFieldInput.ts +180 -0
  41. package/src/core/field/setInitialFieldInput/index.ts +1 -0
  42. package/src/core/field/setInitialFieldInput/setInitialFieldInput.ts +99 -0
  43. package/src/core/field/walkFieldStore/index.ts +1 -0
  44. package/src/core/field/walkFieldStore/walkFieldStore.ts +49 -0
  45. package/src/core/form/createFormStore/createFormStore.ts +56 -0
  46. package/src/core/form/createFormStore/index.ts +1 -0
  47. package/src/core/form/decodeFormData/decodeFormData.ts +436 -0
  48. package/src/core/form/decodeFormData/index.ts +1 -0
  49. package/src/core/form/index.ts +4 -0
  50. package/src/core/form/validateFormInput/index.ts +1 -0
  51. package/src/core/form/validateFormInput/validateFormInput.ts +138 -0
  52. package/src/core/form/validateIfRequired/index.ts +1 -0
  53. package/src/core/form/validateIfRequired/validateIfRequired.ts +31 -0
  54. package/src/core/framework/index.ts +80 -0
  55. package/src/core/index.ts +6 -0
  56. package/src/core/types/field/field.ts +201 -0
  57. package/src/core/types/field/index.ts +1 -0
  58. package/src/core/types/form/form.ts +140 -0
  59. package/src/core/types/form/index.ts +1 -0
  60. package/src/core/types/index.ts +6 -0
  61. package/src/core/types/path/index.ts +10 -0
  62. package/src/core/types/path/path.ts +301 -0
  63. package/src/core/types/schema/index.ts +1 -0
  64. package/src/core/types/schema/schema.ts +18 -0
  65. package/src/core/types/signal/index.ts +1 -0
  66. package/src/core/types/signal/signal.ts +23 -0
  67. package/src/core/types/utils/index.ts +1 -0
  68. package/src/core/types/utils/utils.ts +46 -0
  69. package/src/core/values.ts +4 -0
  70. package/src/hooks/index.ts +3 -0
  71. package/src/hooks/useField/index.ts +1 -0
  72. package/src/hooks/useField/useField.ts +114 -0
  73. package/src/hooks/useFieldArray/index.ts +1 -0
  74. package/src/hooks/useFieldArray/useFieldArray.ts +63 -0
  75. package/src/hooks/useForm/index.ts +1 -0
  76. package/src/hooks/useForm/useForm.ts +68 -0
  77. package/src/hooks/useSignals/index.ts +1 -0
  78. package/src/hooks/useSignals/useSignals.ts +31 -0
  79. package/src/index.ts +19 -0
  80. package/src/internal.ts +32 -0
  81. package/src/methods/focus/focus.ts +35 -0
  82. package/src/methods/focus/index.ts +1 -0
  83. package/src/methods/getDeepErrorEntries/getDeepErrorEntries.ts +108 -0
  84. package/src/methods/getDeepErrorEntries/index.ts +1 -0
  85. package/src/methods/getDeepErrors/getDeepErrors.ts +90 -0
  86. package/src/methods/getDeepErrors/index.ts +1 -0
  87. package/src/methods/getDirtyInput/getDirtyInput.ts +87 -0
  88. package/src/methods/getDirtyInput/index.ts +1 -0
  89. package/src/methods/getDirtyPaths/getDirtyPaths.ts +123 -0
  90. package/src/methods/getDirtyPaths/index.ts +1 -0
  91. package/src/methods/getErrors/getErrors.ts +70 -0
  92. package/src/methods/getErrors/index.ts +1 -0
  93. package/src/methods/getInput/getInput.ts +75 -0
  94. package/src/methods/getInput/index.ts +1 -0
  95. package/src/methods/handleSubmit/handleSubmit.ts +83 -0
  96. package/src/methods/handleSubmit/index.ts +1 -0
  97. package/src/methods/index.ts +23 -0
  98. package/src/methods/insert/index.ts +1 -0
  99. package/src/methods/insert/insert.ts +134 -0
  100. package/src/methods/isDirty/index.ts +1 -0
  101. package/src/methods/isDirty/isDirty.ts +72 -0
  102. package/src/methods/isEdited/index.ts +1 -0
  103. package/src/methods/isEdited/isEdited.ts +72 -0
  104. package/src/methods/isTouched/index.ts +1 -0
  105. package/src/methods/isTouched/isTouched.ts +72 -0
  106. package/src/methods/isValid/index.ts +1 -0
  107. package/src/methods/isValid/isValid.ts +74 -0
  108. package/src/methods/move/index.ts +1 -0
  109. package/src/methods/move/move.ts +124 -0
  110. package/src/methods/pickDirty/index.ts +1 -0
  111. package/src/methods/pickDirty/pickDirty.ts +87 -0
  112. package/src/methods/remove/index.ts +1 -0
  113. package/src/methods/remove/remove.ts +76 -0
  114. package/src/methods/replace/index.ts +1 -0
  115. package/src/methods/replace/replace.ts +80 -0
  116. package/src/methods/reset/index.ts +1 -0
  117. package/src/methods/reset/reset.ts +216 -0
  118. package/src/methods/setErrors/index.ts +1 -0
  119. package/src/methods/setErrors/setErrors.ts +63 -0
  120. package/src/methods/setInput/index.ts +1 -0
  121. package/src/methods/setInput/setInput.ts +87 -0
  122. package/src/methods/submit/index.ts +1 -0
  123. package/src/methods/submit/submit.ts +11 -0
  124. package/src/methods/swap/index.ts +1 -0
  125. package/src/methods/swap/swap.ts +85 -0
  126. package/src/methods/validate/index.ts +1 -0
  127. package/src/methods/validate/validate.ts +34 -0
  128. package/src/types/field.ts +48 -0
  129. package/src/types/form.ts +12 -0
  130. package/src/types/index.ts +2 -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,73 @@
1
+ # @octanejs/formisch
2
+
3
+ Octane-native bindings for Formisch.
4
+
5
+ This package targets Formisch `1.0.0-rc.0` and vendors its framework-selected
6
+ core and modular methods at commit
7
+ `4c494fd8cf105efd04a4b179e9c090595a0bf041`. It does not require React at runtime
8
+ or in its public types.
9
+
10
+ Formisch's core and method packages are workspace internals rather than
11
+ independently installable npm dependencies. The published `@formisch/react`
12
+ package is used only as a development-time differential oracle.
13
+
14
+ The adapter preserves Formisch's programmatic `onChange` and validation-mode
15
+ names. Native text controls use `onInput`; selects, checkboxes, and radios use
16
+ native `onChange`.
17
+
18
+ ## Install
19
+
20
+ ```sh
21
+ pnpm add @octanejs/formisch valibot
22
+ ```
23
+
24
+ ## Example
25
+
26
+ ```tsx
27
+ import { Form, useField, useForm } from '@octanejs/formisch';
28
+ import * as v from 'valibot';
29
+
30
+ const schema = v.object({
31
+ email: v.pipe(v.string(), v.email('Enter a valid email')),
32
+ });
33
+
34
+ function EmailForm() @{
35
+ const form = useForm({ schema, validate: 'input' });
36
+ const email = useField(form, { path: ['email'] });
37
+
38
+ <Form of={form} onSubmit={async (output, event) => {
39
+ console.log(output.email, event);
40
+ }}>
41
+ <input
42
+ name={email.props.name}
43
+ ref={email.props.ref}
44
+ value={email.input as string}
45
+ onInput={email.props.onInput}
46
+ onBlur={email.props.onBlur}
47
+ />
48
+ @if (email.errors) {
49
+ <p>{email.errors[0]}</p>
50
+ }
51
+ <button type="submit">Submit</button>
52
+ </Form>
53
+ }
54
+ ```
55
+
56
+ `useField` also exposes `props.onChange` for native select, checkbox, and radio
57
+ controls. Its programmatic `field.onChange(value)` callback keeps Formisch's
58
+ upstream name and triggers input/change validation.
59
+
60
+ All Formisch modular methods—including `getInput`, `setInput`, `validate`,
61
+ `handleSubmit`, `reset`, and field-array operations—are exported from the package
62
+ root.
63
+
64
+ ## Compatibility
65
+
66
+ - Upstream: `@formisch/react@1.0.0-rc.0`
67
+ - Valibot: `^1.4.1`
68
+ - Octane: exact workspace peer during development
69
+ - React and React types: development-only differential oracle; neither is a
70
+ runtime or public-type dependency
71
+
72
+ See [UPSTREAM.md](./UPSTREAM.md) for source provenance and `status.json` for the
73
+ current divergence ledger.
package/UPSTREAM.md ADDED
@@ -0,0 +1,95 @@
1
+ # Upstream provenance and parity crosswalk
2
+
3
+ - Repository: https://github.com/open-circle/formisch
4
+ - Package: `@formisch/react@1.0.0-rc.0`
5
+ - Tag commit: `4c494fd8cf105efd04a4b179e9c090595a0bf041`
6
+ - Advertised compatibility: exactly `1.0.0-rc.0`
7
+ - React oracle: `@formisch/react@1.0.0-rc.0`, React 19.2.7
8
+ - Schema peer: Valibot `^1.4.1`
9
+ - License: MIT
10
+
11
+ The byte-exact upstream source, tests, package metadata, and licenses are kept
12
+ under `upstream/frameworks/react`, `upstream/packages/core`, and
13
+ `upstream/packages/methods`. Those files are development evidence and are not
14
+ published by `@octanejs/formisch`.
15
+
16
+ The npm package contains only compiled output. The canonical repository at the
17
+ pinned commit supplies the source and test boundaries. `src/core` and
18
+ `src/methods` mirror the React-selected core and methods modules because neither
19
+ workspace package is published independently at this release. The React adapter
20
+ is re-authored with Octane hooks and `.tsrx` components.
21
+
22
+ ## Public export crosswalk
23
+
24
+ | Upstream export | Disposition | Evidence |
25
+ | --- | --- | --- |
26
+ | `Field`, `FieldProps`, `FieldStore`, `FieldElementProps` | Ported | `src/components/Field/Field.tsrx`, `src/hooks/useField/useField.ts`; form, hydration, and public-type tests |
27
+ | `FieldArray`, `FieldArrayProps`, `FieldArrayStore` | Ported | `src/components/FieldArray/FieldArray.tsrx`, `src/hooks/useFieldArray/useFieldArray.ts`; form and public-type tests |
28
+ | `Form`, `FormProps`, `FormStore` | Ported | `src/components/Form/Form.tsrx`, `src/hooks/useForm/useForm.ts`; form, SSR, hydration, and public-type tests |
29
+ | `UseFieldConfig`, `useField` | Ported | `src/hooks/useField/useField.ts`; form, differential, hydration, and public-type tests |
30
+ | `UseFieldArrayConfig`, `useFieldArray` | Ported | `src/hooks/useFieldArray/useFieldArray.ts`; form and public-type tests |
31
+ | `useForm` | Ported | `src/hooks/useForm/useForm.ts`; form, differential, SSR, hydration, and public-type tests |
32
+ | `DeepPartial`, `FieldElement`, `FormConfig`, `FormSchema`, `PartialValues`, `PathValue`, `RequiredPath`, `Schema`, `SubmitEventHandler`, `SubmitHandler`, `ValidArrayPath`, `ValidPath`, `ValidationMode` | Reused from the pinned core | `src/core/types`; `typetests/public-api.test-d.ts` |
33
+ | `DeepErrorEntry`, `FocusFieldConfig`, `GetFieldDeepErrorEntriesConfig`, `GetFormDeepErrorEntriesConfig`, `focus`, `getDeepErrorEntries` | Reused from pinned methods | `src/methods/focus`, `src/methods/getDeepErrorEntries`; export test |
34
+ | `GetFieldDeepErrorsConfig`, `GetFormDeepErrorsConfig`, `getDeepErrors` | Reused from pinned methods | `src/methods/getDeepErrors`; export test |
35
+ | `GetFieldDirtyInputConfig`, `GetFormDirtyInputConfig`, `getDirtyInput` | Reused from pinned methods | `src/methods/getDirtyInput`; export test |
36
+ | `GetFieldDirtyPathsConfig`, `GetFormDirtyPathsConfig`, `getDirtyPaths` | Reused from pinned methods | `src/methods/getDirtyPaths`; export test |
37
+ | `GetFieldErrorsConfig`, `GetFormErrorsConfig`, `getErrors` | Reused from pinned methods | `src/methods/getErrors`; export test |
38
+ | `GetFieldInputConfig`, `GetFormInputConfig`, `getInput` | Reused from pinned methods | `src/methods/getInput`; export test |
39
+ | `handleSubmit` | Reused from the React-selected pinned method | `src/methods/handleSubmit`; form test |
40
+ | `InsertConfig`, `insert` | Reused from pinned methods | `src/methods/insert`; form and methods-engine tests |
41
+ | `IsFieldDirtyConfig`, `IsFormDirtyConfig`, `isDirty` | Reused from pinned methods | `src/methods/isDirty`; export test |
42
+ | `IsFieldEditedConfig`, `IsFormEditedConfig`, `isEdited` | Reused from pinned methods | `src/methods/isEdited`; export test |
43
+ | `IsFieldTouchedConfig`, `IsFormTouchedConfig`, `isTouched` | Reused from pinned methods | `src/methods/isTouched`; export test |
44
+ | `IsFieldValidConfig`, `IsFormValidConfig`, `isValid` | Reused from pinned methods | `src/methods/isValid`; export test |
45
+ | `MoveConfig`, `move` | Reused from pinned methods | `src/methods/move`; methods-engine test |
46
+ | `PickDirtyConfig`, `pickDirty` | Reused from pinned methods | `src/methods/pickDirty`; export test |
47
+ | `RemoveConfig`, `remove` | Reused from pinned methods | `src/methods/remove`; methods-engine test and playground |
48
+ | `ReplaceConfig`, `replace` | Reused from pinned methods | `src/methods/replace`; methods-engine test |
49
+ | `ResetFieldConfig`, `ResetFormConfig`, `reset` | Reused from pinned methods | `src/methods/reset`; form and methods-engine tests |
50
+ | `SetFieldErrorsConfig`, `SetFormErrorsConfig`, `setErrors` | Reused from pinned methods | `src/methods/setErrors`; export test |
51
+ | `SetFieldInputConfig`, `SetFormInputConfig`, `setInput` | Reused from pinned methods | `src/methods/setInput`; methods-engine test |
52
+ | `submit` | Reused from pinned methods | `src/methods/submit`; export test |
53
+ | `SwapConfig`, `swap` | Reused from pinned methods | `src/methods/swap`; methods-engine test |
54
+ | `ValidateFormConfig`, `validate` | Reused from pinned methods | `src/methods/validate`; export test |
55
+
56
+ ## Intentional divergences
57
+
58
+ - Text controls expose native `onInput` for per-edit updates. Selects,
59
+ checkboxes, and radios retain native `onChange`.
60
+ - React synthetic event and renderable types become native DOM events and
61
+ `OctaneNode`.
62
+ - React StrictMode's delayed signal cleanup does not apply because Octane does
63
+ not double-invoke components in StrictMode.
64
+
65
+ ## Upstream test disposition
66
+
67
+ The pinned repository has 56 runtime/type test artifacts: 10 in the React
68
+ adapter, 21 in core, and 25 in methods. Every artifact now has an executable
69
+ disposition:
70
+
71
+ - All 18 core runtime files (302 cases) and all 23 methods runtime files (205
72
+ cases) run byte-exact against the pinned implementation and again unchanged
73
+ against the Octane-selected source.
74
+ - All seven React runtime files run byte-exact against React (42 cases) and have
75
+ one-for-one `.tsrx` adaptations with the same 42 case identities. The
76
+ StrictMode replay case remains executable as the declared
77
+ `formisch-no-strictmode-replay` divergence.
78
+ - All eight upstream type-test files run in pristine `tsc` lanes and in
79
+ one-for-one adapted `tsrx-tsc` lanes. The committed inventories cover 164
80
+ test groups and 217 assertions (381 hashed groups in total).
81
+ - The pristine React type overlay keeps the three upstream test files
82
+ byte-exact and supplies only adjacent declaration shims for Formisch's
83
+ build-time framework selector. Overlay byte drift is a failing negative
84
+ control.
85
+
86
+ `audit/react-parity.json` registers all ten required runtime and type lanes, including a port-only resolver canary.
87
+ `audit/type-parity.json` records the allowed import-only type transformations,
88
+ and `audit/test-classifications.json` classifies every port-authored test.
89
+ Negative controls reject missing, renamed, skipped, or structurally changed
90
+ runtime/type evidence.
91
+
92
+ Additional Octane evidence remains deliberately separate from the upstream
93
+ claim: `tests/differential/parity.test.ts` is React/Octane differential
94
+ coverage; conformance, hydration, and SSR files are Octane-only framework or
95
+ divergence contracts.
package/package.json ADDED
@@ -0,0 +1,65 @@
1
+ {
2
+ "name": "@octanejs/formisch",
3
+ "version": "0.0.1",
4
+ "license": "MIT",
5
+ "type": "module",
6
+ "engines": {
7
+ "node": ">=22.22.2"
8
+ },
9
+ "octane": {
10
+ "hookSlots": {
11
+ "manual": [
12
+ "src"
13
+ ]
14
+ }
15
+ },
16
+ "description": "Formisch bindings for Octane, with a vendored React-free Formisch core and modular methods.",
17
+ "author": {
18
+ "name": "Dominic Gannaway",
19
+ "email": "dg@domgan.com"
20
+ },
21
+ "publishConfig": {
22
+ "access": "public"
23
+ },
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "git+https://github.com/octanejs/octane.git",
27
+ "directory": "packages/formisch"
28
+ },
29
+ "main": "src/index.ts",
30
+ "module": "src/index.ts",
31
+ "types": "src/index.ts",
32
+ "files": [
33
+ "src",
34
+ "README.md",
35
+ "UPSTREAM.md"
36
+ ],
37
+ "exports": {
38
+ ".": "./src/index.ts"
39
+ },
40
+ "peerDependencies": {
41
+ "valibot": "^1.4.1",
42
+ "octane": "0.1.46"
43
+ },
44
+ "devDependencies": {
45
+ "@formisch/react": "1.0.0-rc.0",
46
+ "@testing-library/dom": "^10.4.1",
47
+ "@testing-library/jest-dom": "^6.9.1",
48
+ "@testing-library/react": "16.3.2",
49
+ "@testing-library/user-event": "^14.6.1",
50
+ "@tsrx/react": "^0.2.61",
51
+ "@types/react": "^19.2.17",
52
+ "@types/react-dom": "^19.2.3",
53
+ "react": "^19.2.7",
54
+ "react-dom": "^19.2.7",
55
+ "valibot": "^1.4.1",
56
+ "vitest": "^4.1.10",
57
+ "@octanejs/testing-library": "0.1.43",
58
+ "octane": "0.1.46"
59
+ },
60
+ "scripts": {
61
+ "test": "vitest run --root ../.. --project formisch",
62
+ "test:parity": "node ../../scripts/react-parity/formisch-check.mjs",
63
+ "typecheck": "tsrx-tsc --noEmit -p tsconfig.json && tsrx-tsc --noEmit -p typetests/tsconfig.json"
64
+ }
65
+ }
@@ -0,0 +1,22 @@
1
+ import type { FormSchema, RequiredPath, ValidPath } from '../../core/index.ts';
2
+ import type { FieldStore, FormStore } from '../../types/index.ts';
3
+ import { useField } from '../../hooks/useField/useField.ts';
4
+ import type { OctaneNode } from 'octane';
5
+ import type * as v from 'valibot';
6
+
7
+ export interface FieldProps<
8
+ TSchema extends FormSchema = FormSchema,
9
+ TFieldPath extends RequiredPath = RequiredPath,
10
+ > {
11
+ readonly of: FormStore<TSchema>;
12
+ readonly path: ValidPath<v.InferInput<TSchema>, TFieldPath>;
13
+ readonly children: (store: FieldStore<TSchema, TFieldPath>) => OctaneNode;
14
+ }
15
+
16
+ export function Field<TSchema extends FormSchema, TFieldPath extends RequiredPath>({
17
+ of,
18
+ path,
19
+ children,
20
+ }: FieldProps<TSchema, TFieldPath>): OctaneNode {
21
+ return children(useField(of, { path }));
22
+ }
@@ -0,0 +1,17 @@
1
+ import type { FormSchema, RequiredPath, ValidPath } from '../../core/index.ts';
2
+ import type { FieldStore, FormStore } from '../../types/index.ts';
3
+ import type { OctaneNode } from 'octane';
4
+ import type * as v from 'valibot';
5
+
6
+ export interface FieldProps<
7
+ TSchema extends FormSchema = FormSchema,
8
+ TFieldPath extends RequiredPath = RequiredPath,
9
+ > {
10
+ readonly of: FormStore<TSchema>;
11
+ readonly path: ValidPath<v.InferInput<TSchema>, TFieldPath>;
12
+ readonly children: (store: FieldStore<TSchema, TFieldPath>) => OctaneNode;
13
+ }
14
+
15
+ export declare function Field<TSchema extends FormSchema, TFieldPath extends RequiredPath>(
16
+ props: FieldProps<TSchema, TFieldPath>,
17
+ ): OctaneNode;
@@ -0,0 +1 @@
1
+ export * from './Field.tsrx';
@@ -0,0 +1,22 @@
1
+ import type { FormSchema, RequiredPath, ValidArrayPath } from '../../core/index.ts';
2
+ import type { FieldArrayStore, FormStore } from '../../types/index.ts';
3
+ import { useFieldArray } from '../../hooks/useFieldArray/useFieldArray.ts';
4
+ import type { OctaneNode } from 'octane';
5
+ import type * as v from 'valibot';
6
+
7
+ export interface FieldArrayProps<
8
+ TSchema extends FormSchema = FormSchema,
9
+ TFieldArrayPath extends RequiredPath = RequiredPath,
10
+ > {
11
+ readonly of: FormStore<TSchema>;
12
+ readonly path: ValidArrayPath<v.InferInput<TSchema>, TFieldArrayPath>;
13
+ readonly children: (store: FieldArrayStore<TSchema, TFieldArrayPath>) => OctaneNode;
14
+ }
15
+
16
+ export function FieldArray<TSchema extends FormSchema, TFieldArrayPath extends RequiredPath>({
17
+ of,
18
+ path,
19
+ children,
20
+ }: FieldArrayProps<TSchema, TFieldArrayPath>): OctaneNode {
21
+ return children(useFieldArray(of, { path }));
22
+ }
@@ -0,0 +1,18 @@
1
+ import type { FormSchema, RequiredPath, ValidArrayPath } from '../../core/index.ts';
2
+ import type { FieldArrayStore, FormStore } from '../../types/index.ts';
3
+ import type { OctaneNode } from 'octane';
4
+ import type * as v from 'valibot';
5
+
6
+ export interface FieldArrayProps<
7
+ TSchema extends FormSchema = FormSchema,
8
+ TFieldArrayPath extends RequiredPath = RequiredPath,
9
+ > {
10
+ readonly of: FormStore<TSchema>;
11
+ readonly path: ValidArrayPath<v.InferInput<TSchema>, TFieldArrayPath>;
12
+ readonly children: (store: FieldArrayStore<TSchema, TFieldArrayPath>) => OctaneNode;
13
+ }
14
+
15
+ export declare function FieldArray<
16
+ TSchema extends FormSchema,
17
+ TFieldArrayPath extends RequiredPath,
18
+ >(props: FieldArrayProps<TSchema, TFieldArrayPath>): OctaneNode;
@@ -0,0 +1 @@
1
+ export * from './FieldArray.tsrx';
@@ -0,0 +1,28 @@
1
+ import { type FormSchema, INTERNAL, type SubmitEventHandler } from '../../core/index.ts';
2
+ import { handleSubmit } from '../../methods/index.ts';
3
+ import type { FormStore } from '../../types/index.ts';
4
+ import type { OctaneNode } from 'octane';
5
+ import type { Octane } from 'octane/jsx-runtime';
6
+
7
+ export type FormProps<TSchema extends FormSchema = FormSchema> =
8
+ Omit<
9
+ Octane.FormHTMLAttributes<HTMLFormElement>,
10
+ 'onSubmit' | 'novalidate' | 'noValidate' | 'children'
11
+ > & {
12
+ readonly of: FormStore<TSchema>;
13
+ readonly onSubmit: SubmitEventHandler<TSchema>;
14
+ readonly children?: OctaneNode;
15
+ };
16
+
17
+ export function Form<TSchema extends FormSchema>({ of, onSubmit, children, ...other }: FormProps<
18
+ TSchema
19
+ >) @{
20
+ <form
21
+ {...other}
22
+ noValidate
23
+ ref={(element) => {
24
+ if (element) of[INTERNAL].element = element;
25
+ }}
26
+ onSubmit={handleSubmit(of, onSubmit)}
27
+ >{children}</form>
28
+ }
@@ -0,0 +1,15 @@
1
+ import type { FormSchema, SubmitEventHandler } from '../../core/index.ts';
2
+ import type { FormStore } from '../../types/index.ts';
3
+ import type { OctaneNode } from 'octane';
4
+ import type { Octane } from 'octane/jsx-runtime';
5
+
6
+ export type FormProps<TSchema extends FormSchema = FormSchema> = Omit<
7
+ Octane.FormHTMLAttributes<HTMLFormElement>,
8
+ 'onSubmit' | 'novalidate' | 'noValidate' | 'children'
9
+ > & {
10
+ readonly of: FormStore<TSchema>;
11
+ readonly onSubmit: SubmitEventHandler<TSchema>;
12
+ readonly children?: OctaneNode;
13
+ };
14
+
15
+ export declare function Form<TSchema extends FormSchema>(props: FormProps<TSchema>): OctaneNode;
@@ -0,0 +1 @@
1
+ export * from './Form.tsrx';
@@ -0,0 +1,3 @@
1
+ export * from './Field/index.ts';
2
+ export * from './FieldArray/index.ts';
3
+ export * from './Form/index.ts';
@@ -0,0 +1,101 @@
1
+ import { initializeFieldStore } from '../../field/initializeFieldStore/index.ts';
2
+ import { batch, untrack } from '../../framework/index.ts';
3
+ import type { InternalFieldStore, InternalFormStore } from '../../types/index.ts';
4
+
5
+ /**
6
+ * Copies the deeply nested state (signal values) from one field store to
7
+ * another. This includes the `elements`, `errors`, `startInput`, `input`,
8
+ * `isTouched`, `isEdited`, `isDirty`, and for arrays `startItems` and `items`
9
+ * properties. Recursively walks through the field stores and copies all signal
10
+ * values.
11
+ *
12
+ * @param internalFormStore The form store providing the empty input config.
13
+ * @param fromInternalFieldStore The source field store to copy from.
14
+ * @param toInternalFieldStore The destination field store to copy to.
15
+ */
16
+ export function copyItemState(
17
+ internalFormStore: InternalFormStore,
18
+ fromInternalFieldStore: InternalFieldStore,
19
+ toInternalFieldStore: InternalFieldStore,
20
+ ): void {
21
+ // Batch all state updates for optimal reactivity performance
22
+ batch(() => {
23
+ // Untrack to avoid creating reactive dependencies during copy operation
24
+ untrack(() => {
25
+ // Copy elements reference
26
+ toInternalFieldStore.elements = fromInternalFieldStore.elements;
27
+
28
+ // Copy errors
29
+ toInternalFieldStore.errors.value = fromInternalFieldStore.errors.value;
30
+
31
+ // Copy start input
32
+ toInternalFieldStore.startInput.value = fromInternalFieldStore.startInput.value;
33
+
34
+ // Copy current input
35
+ toInternalFieldStore.input.value = fromInternalFieldStore.input.value;
36
+
37
+ // Copy touched state
38
+ toInternalFieldStore.isTouched.value = fromInternalFieldStore.isTouched.value;
39
+
40
+ // Copy edited state
41
+ toInternalFieldStore.isEdited.value = fromInternalFieldStore.isEdited.value;
42
+
43
+ // Copy dirty state
44
+ toInternalFieldStore.isDirty.value = fromInternalFieldStore.isDirty.value;
45
+
46
+ // If both stores are arrays, copy array-specific state
47
+ if (fromInternalFieldStore.kind === 'array' && toInternalFieldStore.kind === 'array') {
48
+ // Get source items array
49
+ const fromItems = fromInternalFieldStore.items.value;
50
+
51
+ // Copy start items
52
+ toInternalFieldStore.startItems.value = fromInternalFieldStore.startItems.value;
53
+
54
+ // Copy current items
55
+ toInternalFieldStore.items.value = fromItems;
56
+
57
+ // Copy state for each array item
58
+ for (let index = 0; index < fromItems.length; index++) {
59
+ // If destination child doesn't exist, initialize it
60
+ if (!toInternalFieldStore.children[index]) {
61
+ // Create empty child object
62
+ // @ts-expect-error
63
+ toInternalFieldStore.children[index] = {};
64
+
65
+ // Initialize field store for new child
66
+ initializeFieldStore(
67
+ internalFormStore,
68
+ toInternalFieldStore.children[index],
69
+ // @ts-expect-error
70
+ toInternalFieldStore.schema.item,
71
+ undefined,
72
+ [...toInternalFieldStore.path, index],
73
+ );
74
+ }
75
+
76
+ // Recursively copy child state
77
+ copyItemState(
78
+ internalFormStore,
79
+ fromInternalFieldStore.children[index],
80
+ toInternalFieldStore.children[index],
81
+ );
82
+ }
83
+
84
+ // Otherwise, if both stores are objects, copy object children
85
+ } else if (
86
+ fromInternalFieldStore.kind === 'object' &&
87
+ toInternalFieldStore.kind === 'object'
88
+ ) {
89
+ // Copy state for each object property
90
+ for (const key in fromInternalFieldStore.children) {
91
+ // Recursively copy child state
92
+ copyItemState(
93
+ internalFormStore,
94
+ fromInternalFieldStore.children[key],
95
+ toInternalFieldStore.children[key],
96
+ );
97
+ }
98
+ }
99
+ });
100
+ });
101
+ }
@@ -0,0 +1 @@
1
+ export * from './copyItemState.ts';
@@ -0,0 +1,3 @@
1
+ export * from './copyItemState/index.ts';
2
+ export * from './resetItemState/index.ts';
3
+ export * from './swapItemState/index.ts';
@@ -0,0 +1 @@
1
+ export * from './resetItemState.ts';