@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.
- package/LICENSE +21 -0
- package/README.md +73 -0
- package/UPSTREAM.md +95 -0
- package/package.json +65 -0
- package/src/components/Field/Field.tsrx +22 -0
- package/src/components/Field/Field.tsrx.d.ts +17 -0
- package/src/components/Field/index.ts +1 -0
- package/src/components/FieldArray/FieldArray.tsrx +22 -0
- package/src/components/FieldArray/FieldArray.tsrx.d.ts +18 -0
- package/src/components/FieldArray/index.ts +1 -0
- package/src/components/Form/Form.tsrx +28 -0
- package/src/components/Form/Form.tsrx.d.ts +15 -0
- package/src/components/Form/index.ts +1 -0
- package/src/components/index.ts +3 -0
- package/src/core/array/copyItemState/copyItemState.ts +101 -0
- package/src/core/array/copyItemState/index.ts +1 -0
- package/src/core/array/index.ts +3 -0
- package/src/core/array/resetItemState/index.ts +1 -0
- package/src/core/array/resetItemState/resetItemState.ts +172 -0
- package/src/core/array/swapItemState/index.ts +1 -0
- package/src/core/array/swapItemState/swapItemState.ts +138 -0
- package/src/core/field/focusFieldElement/focusFieldElement.ts +32 -0
- package/src/core/field/focusFieldElement/index.ts +1 -0
- package/src/core/field/getDirtyFieldInput/getDirtyFieldInput.ts +66 -0
- package/src/core/field/getDirtyFieldInput/index.ts +1 -0
- package/src/core/field/getElementInput/getElementInput.ts +78 -0
- package/src/core/field/getElementInput/index.ts +1 -0
- package/src/core/field/getFieldBool/getFieldBool.ts +22 -0
- package/src/core/field/getFieldBool/index.ts +1 -0
- package/src/core/field/getFieldInput/getFieldInput.ts +52 -0
- package/src/core/field/getFieldInput/index.ts +1 -0
- package/src/core/field/getFieldStore/getFieldStore.ts +34 -0
- package/src/core/field/getFieldStore/index.ts +1 -0
- package/src/core/field/index.ts +11 -0
- package/src/core/field/initializeFieldStore/index.ts +1 -0
- package/src/core/field/initializeFieldStore/initializeFieldStore.ts +325 -0
- package/src/core/field/setFieldBool/index.ts +1 -0
- package/src/core/field/setFieldBool/setFieldBool.ts +29 -0
- package/src/core/field/setFieldInput/index.ts +1 -0
- package/src/core/field/setFieldInput/setFieldInput.ts +180 -0
- package/src/core/field/setInitialFieldInput/index.ts +1 -0
- package/src/core/field/setInitialFieldInput/setInitialFieldInput.ts +99 -0
- package/src/core/field/walkFieldStore/index.ts +1 -0
- package/src/core/field/walkFieldStore/walkFieldStore.ts +49 -0
- package/src/core/form/createFormStore/createFormStore.ts +56 -0
- package/src/core/form/createFormStore/index.ts +1 -0
- package/src/core/form/decodeFormData/decodeFormData.ts +436 -0
- package/src/core/form/decodeFormData/index.ts +1 -0
- package/src/core/form/index.ts +4 -0
- package/src/core/form/validateFormInput/index.ts +1 -0
- package/src/core/form/validateFormInput/validateFormInput.ts +138 -0
- package/src/core/form/validateIfRequired/index.ts +1 -0
- package/src/core/form/validateIfRequired/validateIfRequired.ts +31 -0
- package/src/core/framework/index.ts +80 -0
- package/src/core/index.ts +6 -0
- package/src/core/types/field/field.ts +201 -0
- package/src/core/types/field/index.ts +1 -0
- package/src/core/types/form/form.ts +140 -0
- package/src/core/types/form/index.ts +1 -0
- package/src/core/types/index.ts +6 -0
- package/src/core/types/path/index.ts +10 -0
- package/src/core/types/path/path.ts +301 -0
- package/src/core/types/schema/index.ts +1 -0
- package/src/core/types/schema/schema.ts +18 -0
- package/src/core/types/signal/index.ts +1 -0
- package/src/core/types/signal/signal.ts +23 -0
- package/src/core/types/utils/index.ts +1 -0
- package/src/core/types/utils/utils.ts +46 -0
- package/src/core/values.ts +4 -0
- package/src/hooks/index.ts +3 -0
- package/src/hooks/useField/index.ts +1 -0
- package/src/hooks/useField/useField.ts +114 -0
- package/src/hooks/useFieldArray/index.ts +1 -0
- package/src/hooks/useFieldArray/useFieldArray.ts +63 -0
- package/src/hooks/useForm/index.ts +1 -0
- package/src/hooks/useForm/useForm.ts +68 -0
- package/src/hooks/useSignals/index.ts +1 -0
- package/src/hooks/useSignals/useSignals.ts +31 -0
- package/src/index.ts +19 -0
- package/src/internal.ts +32 -0
- package/src/methods/focus/focus.ts +35 -0
- package/src/methods/focus/index.ts +1 -0
- package/src/methods/getDeepErrorEntries/getDeepErrorEntries.ts +108 -0
- package/src/methods/getDeepErrorEntries/index.ts +1 -0
- package/src/methods/getDeepErrors/getDeepErrors.ts +90 -0
- package/src/methods/getDeepErrors/index.ts +1 -0
- package/src/methods/getDirtyInput/getDirtyInput.ts +87 -0
- package/src/methods/getDirtyInput/index.ts +1 -0
- package/src/methods/getDirtyPaths/getDirtyPaths.ts +123 -0
- package/src/methods/getDirtyPaths/index.ts +1 -0
- package/src/methods/getErrors/getErrors.ts +70 -0
- package/src/methods/getErrors/index.ts +1 -0
- package/src/methods/getInput/getInput.ts +75 -0
- package/src/methods/getInput/index.ts +1 -0
- package/src/methods/handleSubmit/handleSubmit.ts +83 -0
- package/src/methods/handleSubmit/index.ts +1 -0
- package/src/methods/index.ts +23 -0
- package/src/methods/insert/index.ts +1 -0
- package/src/methods/insert/insert.ts +134 -0
- package/src/methods/isDirty/index.ts +1 -0
- package/src/methods/isDirty/isDirty.ts +72 -0
- package/src/methods/isEdited/index.ts +1 -0
- package/src/methods/isEdited/isEdited.ts +72 -0
- package/src/methods/isTouched/index.ts +1 -0
- package/src/methods/isTouched/isTouched.ts +72 -0
- package/src/methods/isValid/index.ts +1 -0
- package/src/methods/isValid/isValid.ts +74 -0
- package/src/methods/move/index.ts +1 -0
- package/src/methods/move/move.ts +124 -0
- package/src/methods/pickDirty/index.ts +1 -0
- package/src/methods/pickDirty/pickDirty.ts +87 -0
- package/src/methods/remove/index.ts +1 -0
- package/src/methods/remove/remove.ts +76 -0
- package/src/methods/replace/index.ts +1 -0
- package/src/methods/replace/replace.ts +80 -0
- package/src/methods/reset/index.ts +1 -0
- package/src/methods/reset/reset.ts +216 -0
- package/src/methods/setErrors/index.ts +1 -0
- package/src/methods/setErrors/setErrors.ts +63 -0
- package/src/methods/setInput/index.ts +1 -0
- package/src/methods/setInput/setInput.ts +87 -0
- package/src/methods/submit/index.ts +1 -0
- package/src/methods/submit/submit.ts +11 -0
- package/src/methods/swap/index.ts +1 -0
- package/src/methods/swap/swap.ts +85 -0
- package/src/methods/validate/index.ts +1 -0
- package/src/methods/validate/validate.ts +34 -0
- package/src/types/field.ts +48 -0
- package/src/types/form.ts +12 -0
- 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,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 @@
|
|
|
1
|
+
export * from './resetItemState.ts';
|