@effect-app/vue-components 4.0.0-beta.27 → 4.0.0-beta.271

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 (198) hide show
  1. package/README.md +13 -9
  2. package/dist/reset.css +39 -38
  3. package/dist/types/components/CommandButton.vue.d.ts +24 -5
  4. package/dist/types/components/OmegaForm/InputProps.d.ts +1 -1
  5. package/dist/types/components/OmegaForm/OmegaArray.vue.d.ts +1 -1
  6. package/dist/types/components/OmegaForm/OmegaAutoGen.vue.d.ts +2 -2
  7. package/dist/types/components/OmegaForm/OmegaErrorsInternal.vue.d.ts +1 -1
  8. package/dist/types/components/OmegaForm/OmegaFormInput.vue.d.ts +1 -1
  9. package/dist/types/components/OmegaForm/OmegaInput.vue.d.ts +1 -1
  10. package/dist/types/components/OmegaForm/OmegaInternalInput.vue.d.ts +2 -1
  11. package/dist/types/components/OmegaForm/OmegaTaggedUnion.vue.d.ts +2 -2
  12. package/dist/types/components/OmegaForm/OmegaTaggedUnionInternal.vue.d.ts +3 -3
  13. package/dist/types/components/OmegaForm/OmegaWrapper.vue.d.ts +1 -1
  14. package/dist/types/components/OmegaForm/createUseFormWithCustomInput.d.ts +2 -2
  15. package/dist/types/components/OmegaForm/errors.d.ts +33 -0
  16. package/dist/types/components/OmegaForm/getOmegaStore.d.ts +1 -1
  17. package/dist/types/components/OmegaForm/hocs.d.ts +3 -0
  18. package/dist/types/components/OmegaForm/index.d.ts +13 -3
  19. package/dist/types/components/OmegaForm/inputs.d.ts +4 -0
  20. package/dist/types/components/OmegaForm/meta/checks.d.ts +4 -0
  21. package/dist/types/components/OmegaForm/meta/createMeta.d.ts +33 -0
  22. package/dist/types/components/OmegaForm/meta/defaults.d.ts +21 -0
  23. package/dist/types/components/OmegaForm/meta/redacted.d.ts +2 -0
  24. package/dist/types/components/OmegaForm/meta/types.d.ts +56 -0
  25. package/dist/types/components/OmegaForm/meta/walker.d.ts +18 -0
  26. package/dist/types/components/OmegaForm/persistency.d.ts +58 -0
  27. package/dist/types/components/OmegaForm/submit.d.ts +60 -0
  28. package/dist/types/components/OmegaForm/types.d.ts +289 -0
  29. package/dist/types/components/OmegaForm/useOmegaForm.d.ts +7 -213
  30. package/dist/types/components/OmegaForm/validation/localized.d.ts +10 -0
  31. package/dist/types/index.d.ts +0 -1
  32. package/dist/types/utils/index.d.ts +8 -8
  33. package/dist/vue-components.es.js +29 -44
  34. package/dist/vue-components10.es.js +5 -0
  35. package/dist/vue-components11.es.js +20 -0
  36. package/dist/vue-components12.es.js +49 -0
  37. package/dist/vue-components13.es.js +128 -0
  38. package/dist/vue-components14.es.js +65 -0
  39. package/dist/vue-components15.es.js +114 -0
  40. package/dist/vue-components16.es.js +22 -0
  41. package/dist/vue-components17.es.js +5 -0
  42. package/dist/vue-components18.es.js +80 -0
  43. package/dist/vue-components19.es.js +93 -0
  44. package/dist/vue-components2.es.js +11 -0
  45. package/dist/vue-components20.es.js +73 -0
  46. package/dist/vue-components21.es.js +12 -0
  47. package/dist/vue-components22.es.js +56 -0
  48. package/dist/vue-components23.es.js +5 -0
  49. package/dist/vue-components24.es.js +44 -0
  50. package/dist/vue-components25.es.js +5 -0
  51. package/dist/vue-components26.es.js +84 -0
  52. package/dist/vue-components28.es.js +8 -0
  53. package/dist/vue-components29.es.js +9 -0
  54. package/dist/vue-components3.es.js +98 -0
  55. package/dist/vue-components30.es.js +269 -0
  56. package/dist/vue-components32.es.js +8 -0
  57. package/dist/vue-components33.es.js +73 -0
  58. package/dist/vue-components34.es.js +5 -0
  59. package/dist/vue-components35.es.js +52 -0
  60. package/dist/vue-components36.es.js +5 -0
  61. package/dist/vue-components37.es.js +24 -0
  62. package/dist/vue-components38.es.js +5 -0
  63. package/dist/vue-components39.es.js +59 -0
  64. package/dist/vue-components4.es.js +5 -0
  65. package/dist/vue-components40.es.js +5 -0
  66. package/dist/vue-components41.es.js +12 -0
  67. package/dist/vue-components42.es.js +22 -0
  68. package/dist/vue-components44.es.js +9 -0
  69. package/dist/vue-components45.es.js +4 -0
  70. package/dist/vue-components46.es.js +38 -0
  71. package/dist/vue-components47.es.js +27 -0
  72. package/dist/vue-components48.es.js +28 -0
  73. package/dist/vue-components49.es.js +7 -0
  74. package/dist/vue-components5.es.js +24 -0
  75. package/dist/vue-components50.es.js +18 -0
  76. package/dist/vue-components51.es.js +36 -0
  77. package/dist/vue-components52.es.js +18 -0
  78. package/dist/vue-components53.es.js +21 -0
  79. package/dist/vue-components54.es.js +30 -0
  80. package/dist/vue-components55.es.js +7 -0
  81. package/dist/vue-components56.es.js +9 -0
  82. package/dist/vue-components57.es.js +38 -0
  83. package/dist/vue-components58.es.js +25 -0
  84. package/dist/vue-components59.es.js +128 -0
  85. package/dist/vue-components6.es.js +13 -0
  86. package/dist/vue-components60.es.js +24 -0
  87. package/dist/vue-components61.es.js +21 -0
  88. package/dist/vue-components62.es.js +9 -0
  89. package/dist/vue-components63.es.js +19 -0
  90. package/dist/vue-components64.es.js +5 -0
  91. package/dist/vue-components65.es.js +29 -0
  92. package/dist/vue-components66.es.js +5 -0
  93. package/dist/vue-components67.es.js +43 -0
  94. package/dist/vue-components68.es.js +100 -0
  95. package/dist/vue-components69.es.js +33 -0
  96. package/dist/vue-components7.es.js +13 -0
  97. package/dist/vue-components70.es.js +19 -0
  98. package/dist/vue-components71.es.js +48 -0
  99. package/dist/vue-components8.es.js +35 -0
  100. package/dist/vue-components9.es.js +47 -0
  101. package/package.json +30 -30
  102. package/src/components/CommandButton.vue +96 -16
  103. package/src/components/OmegaForm/InputProps.ts +1 -1
  104. package/src/components/OmegaForm/OmegaArray.vue +8 -9
  105. package/src/components/OmegaForm/OmegaAutoGen.vue +3 -2
  106. package/src/components/OmegaForm/OmegaErrorsInternal.vue +1 -1
  107. package/src/components/OmegaForm/OmegaFormInput.vue +1 -1
  108. package/src/components/OmegaForm/OmegaInput.vue +15 -38
  109. package/src/components/OmegaForm/OmegaInputVuetify.vue +5 -2
  110. package/src/components/OmegaForm/OmegaInternalInput.vue +17 -5
  111. package/src/components/OmegaForm/OmegaTaggedUnion.vue +10 -3
  112. package/src/components/OmegaForm/OmegaTaggedUnionInternal.vue +6 -6
  113. package/src/components/OmegaForm/OmegaWrapper.vue +1 -1
  114. package/src/components/OmegaForm/blockDialog.ts +18 -6
  115. package/src/components/OmegaForm/createUseFormWithCustomInput.ts +2 -1
  116. package/src/components/OmegaForm/errors.ts +136 -0
  117. package/src/components/OmegaForm/getOmegaStore.ts +1 -1
  118. package/src/components/OmegaForm/hocs.ts +19 -0
  119. package/src/components/OmegaForm/index.ts +16 -4
  120. package/src/components/OmegaForm/inputs.ts +22 -0
  121. package/src/components/OmegaForm/meta/checks.ts +82 -0
  122. package/src/components/OmegaForm/meta/createMeta.ts +140 -0
  123. package/src/components/OmegaForm/meta/defaults.ts +261 -0
  124. package/src/components/OmegaForm/meta/redacted.ts +66 -0
  125. package/src/components/OmegaForm/meta/types.ts +78 -0
  126. package/src/components/OmegaForm/meta/walker.ts +248 -0
  127. package/src/components/OmegaForm/persistency.ts +247 -0
  128. package/src/components/OmegaForm/submit.ts +131 -0
  129. package/src/components/OmegaForm/types.ts +759 -0
  130. package/src/components/OmegaForm/useOmegaForm.ts +99 -893
  131. package/src/components/OmegaForm/useRegisterField.ts +1 -1
  132. package/src/components/OmegaForm/validation/localized.ts +203 -0
  133. package/src/index.ts +0 -1
  134. package/src/reset.css +39 -38
  135. package/src/utils/index.ts +11 -8
  136. package/dist/types/components/OmegaForm/OmegaFormStuff.d.ts +0 -159
  137. package/dist/types/constants/index.d.ts +0 -1
  138. package/dist/vue-components.es10.js +0 -239
  139. package/dist/vue-components.es11.js +0 -32
  140. package/dist/vue-components.es12.js +0 -503
  141. package/dist/vue-components.es13.js +0 -49
  142. package/dist/vue-components.es14.js +0 -4
  143. package/dist/vue-components.es15.js +0 -4
  144. package/dist/vue-components.es16.js +0 -13
  145. package/dist/vue-components.es17.js +0 -6
  146. package/dist/vue-components.es18.js +0 -13
  147. package/dist/vue-components.es19.js +0 -57
  148. package/dist/vue-components.es2.js +0 -30
  149. package/dist/vue-components.es20.js +0 -56
  150. package/dist/vue-components.es21.js +0 -8
  151. package/dist/vue-components.es22.js +0 -8
  152. package/dist/vue-components.es23.js +0 -5
  153. package/dist/vue-components.es24.js +0 -5
  154. package/dist/vue-components.es25.js +0 -4
  155. package/dist/vue-components.es26.js +0 -4
  156. package/dist/vue-components.es27.js +0 -4
  157. package/dist/vue-components.es28.js +0 -4
  158. package/dist/vue-components.es29.js +0 -19
  159. package/dist/vue-components.es3.js +0 -17
  160. package/dist/vue-components.es30.js +0 -194
  161. package/dist/vue-components.es32.js +0 -31
  162. package/dist/vue-components.es33.js +0 -6
  163. package/dist/vue-components.es34.js +0 -4
  164. package/dist/vue-components.es35.js +0 -4
  165. package/dist/vue-components.es36.js +0 -113
  166. package/dist/vue-components.es38.js +0 -9
  167. package/dist/vue-components.es39.js +0 -34
  168. package/dist/vue-components.es4.js +0 -52
  169. package/dist/vue-components.es41.js +0 -6
  170. package/dist/vue-components.es42.js +0 -25
  171. package/dist/vue-components.es43.js +0 -7
  172. package/dist/vue-components.es44.js +0 -23
  173. package/dist/vue-components.es45.js +0 -32
  174. package/dist/vue-components.es46.js +0 -24
  175. package/dist/vue-components.es47.js +0 -14
  176. package/dist/vue-components.es48.js +0 -7
  177. package/dist/vue-components.es49.js +0 -21
  178. package/dist/vue-components.es5.js +0 -52
  179. package/dist/vue-components.es50.js +0 -11
  180. package/dist/vue-components.es51.js +0 -33
  181. package/dist/vue-components.es52.js +0 -50
  182. package/dist/vue-components.es53.js +0 -28
  183. package/dist/vue-components.es54.js +0 -13
  184. package/dist/vue-components.es55.js +0 -31
  185. package/dist/vue-components.es56.js +0 -67
  186. package/dist/vue-components.es57.js +0 -58
  187. package/dist/vue-components.es58.js +0 -19
  188. package/dist/vue-components.es59.js +0 -35
  189. package/dist/vue-components.es6.js +0 -69
  190. package/dist/vue-components.es60.js +0 -44
  191. package/dist/vue-components.es61.js +0 -4
  192. package/dist/vue-components.es62.js +0 -46
  193. package/dist/vue-components.es63.js +0 -4
  194. package/dist/vue-components.es7.js +0 -83
  195. package/dist/vue-components.es8.js +0 -63
  196. package/dist/vue-components.es9.js +0 -21
  197. package/src/components/OmegaForm/OmegaFormStuff.ts +0 -1276
  198. package/src/constants/index.ts +0 -1
@@ -1,672 +1,36 @@
1
1
  /* eslint-disable @typescript-eslint/consistent-type-imports */
2
2
  /* eslint-disable @typescript-eslint/no-explicit-any */
3
3
 
4
- import * as api from "@opentelemetry/api"
5
- import { type DeepKeys, DeepValue, type FormAsyncValidateOrFn, type FormValidateOrFn, type StandardSchemaV1, StandardSchemaV1Issue, useForm, ValidationError, ValidationErrorMap } from "@tanstack/vue-form"
6
- import { Array, Data, Effect, Fiber, Option, Order, S } from "effect-app"
7
- import { runtimeFiberAsPromise, UnionToTuples } from "effect-app/utils"
8
- import { Component, computed, ComputedRef, ConcreteComponent, h, type InjectionKey, onBeforeUnmount, onMounted, onUnmounted, Ref, ref, watch } from "vue"
9
- import { useIntl } from "../../utils"
10
- import { MergedInputProps } from "./InputProps"
4
+ import { type DeepKeys, type DeepValue, type FormAsyncValidateOrFn, type FormValidateOrFn, revalidateLogic, type StandardSchemaV1, useForm } from "@tanstack/vue-form"
5
+ import * as Context from "effect-app/Context"
6
+ import * as S from "effect-app/Schema"
7
+ import { type InjectionKey, watch } from "vue"
8
+ import { eHoc, makeFieldMap } from "./errors"
9
+ import { fHoc } from "./hocs"
10
+ import { generateMetaFromSchema, unwrapDeclaration } from "./meta/createMeta"
11
+ import { defaultsValueFromSchema, fillNestedDefaults } from "./meta/defaults"
12
+ import { toFormSchema } from "./meta/redacted"
11
13
  import OmegaArray from "./OmegaArray.vue"
12
14
  import OmegaAutoGen from "./OmegaAutoGen.vue"
13
15
  import OmegaErrorsInternal from "./OmegaErrorsInternal.vue"
14
- import { BaseProps, deepMerge, defaultsValueFromSchema, DefaultTypeProps, FieldPath, type FormProps, generateMetaFromSchema, type MetaRecord, type NestedKeyOf, OmegaArrayProps, OmegaAutoGenMeta, OmegaError, type OmegaFormApi, OmegaFormState } from "./OmegaFormStuff"
15
16
  import OmegaInput from "./OmegaInput.vue"
16
17
  import OmegaTaggedUnion from "./OmegaTaggedUnion.vue"
17
18
  import OmegaForm from "./OmegaWrapper.vue"
19
+ import { usePersistency } from "./persistency"
20
+ import { makeSubmitHandlers, wrapOnSubmit } from "./submit"
21
+ import type { DefaultTypeProps, FormProps, OF, OmegaConfig, OmegaFormApi, OmegaFormReturn } from "./types"
22
+ import { annotateLiteralUnionMessages, toLocalizedStandardSchemaV1 } from "./validation/localized"
18
23
 
19
- type keysRule<T> =
20
- | {
21
- keys?: NestedKeyOf<T>[]
22
- banKeys?: "You should only use one of banKeys or keys, not both, moron"
23
- }
24
- | {
25
- keys?: "You should only use one of banKeys or keys, not both, moron"
26
- banKeys?: NestedKeyOf<T>[]
27
- }
28
-
29
- export class FormErrors<From> extends Data.TaggedError("FormErrors")<{
30
- form: {
31
- // TODO: error shapes seem off, with `undefined` etc..
32
- errors: (Record<string, StandardSchemaV1Issue[]> | undefined)[]
33
- errorMap: ValidationErrorMap<
34
- undefined,
35
- undefined,
36
- Record<string, StandardSchemaV1Issue[]>,
37
- undefined,
38
- undefined,
39
- undefined,
40
- undefined,
41
- undefined,
42
- undefined,
43
- undefined
44
- >
45
- }
46
- fields: Record<DeepKeys<From>, {
47
- errors: ValidationError[]
48
- errorMap: ValidationErrorMap
49
- }>
50
- }> {}
51
-
52
- const fHoc = (form: OF<any, any>) => {
53
- return function FormHoc<P>(
54
- WrappedComponent: Component<P>
55
- ): ConcreteComponent<P> {
56
- return {
57
- render() {
58
- return h(WrappedComponent, {
59
- form,
60
- ...this.$attrs
61
- } as any, this.$slots)
62
- }
63
- }
64
- }
65
- }
66
-
67
- export const useErrorLabel = (form: OF<any, any>) => {
68
- const { formatMessage } = useIntl()
69
- const humanize = (str: string) => {
70
- return str
71
- .replace(/([A-Z])/g, " $1") // Add space before capital letters
72
- .replace(/^./, (char) => char.toUpperCase()) // Capitalize the first letter
73
- .trim() // Remove leading/trailing spaces
74
- }
75
- const fallback = (propsName: string) =>
76
- formatMessage
77
- ? formatMessage({ id: `general.fields.${propsName}`, defaultMessage: humanize(propsName) })
78
- : humanize(propsName)
79
- const i18n = (propsName: string) =>
80
- form.i18nNamespace
81
- ? formatMessage({ id: `${form.i18nNamespace}.fields.${propsName}`, defaultMessage: fallback(propsName) })
82
- : fallback(propsName)
83
-
84
- return i18n
85
- }
86
-
87
- const eHoc = (errorProps: {
88
- form: OF<any, any>
89
- fieldMap: Ref<Map<string, { id: string; label: string }>>
90
- }) => {
91
- return function FormHoc<P>(
92
- WrappedComponent: Component<P>
93
- ): ConcreteComponent<P> {
94
- return {
95
- setup() {
96
- const { fieldMap, form } = errorProps
97
- const generalErrors = form.useStore((state) => state.errors)
98
- const fieldMeta = form.useStore((state) => state.fieldMeta)
99
- const errorMap = form.useStore((state) => state.errorMap)
100
-
101
- const errorLabel = useErrorLabel(form)
102
-
103
- const errors = computed(() => {
104
- // Collect errors from fieldMeta (field-level errors for registered fields)
105
- const fieldErrors = Object.entries(fieldMeta.value).reduce<OmegaError[]>((acc, [key, m]) => {
106
- const fieldErrors = (m as { errors?: Array<{ message?: string }> } | undefined)?.errors ?? []
107
- if (!fieldErrors.length) {
108
- return acc
109
- }
110
-
111
- const fieldInfo = fieldMap.value.get(key)
112
- if (!fieldInfo) {
113
- return acc
114
- }
115
-
116
- acc.push({
117
- label: fieldInfo.label,
118
- inputId: fieldInfo.id,
119
- errors: [fieldErrors[0]?.message].filter(Boolean) as string[]
120
- })
121
-
122
- return acc
123
- }, [])
124
-
125
- // Collect errors from errorMap.onSubmit ONLY for fields that are NOT registered
126
- // (registered fields already have their errors in fieldMeta)
127
- const submitErrors: OmegaError[] = []
128
- if (errorMap.value.onSubmit) {
129
- for (const [_, issues] of Object.entries(errorMap.value.onSubmit)) {
130
- if (Array.isArray(issues) && issues.length) {
131
- for (const issue of issues) {
132
- const issAny: any = issue
133
- if (issAny?.path && Array.isArray(issAny.path) && issAny.path.length) {
134
- // Use the path from the issue to identify the field
135
- const fieldPath = issAny.path.join(".")
136
- // Only add errors for fields that are NOT registered (not in fieldMap)
137
- // Registered fields will already have their errors from fieldMeta
138
- if (!fieldMap.value.has(fieldPath)) {
139
- submitErrors.push({
140
- label: errorLabel(fieldPath),
141
- inputId: fieldPath,
142
- errors: [issAny.message].filter(Boolean)
143
- })
144
- // Only show first error per field, so break after adding
145
- break
146
- }
147
- }
148
- }
149
- }
150
- }
151
- }
152
-
153
- // Combine both error sources (no need to check for duplicates since they're mutually exclusive)
154
- return [...fieldErrors, ...submitErrors]
155
- })
156
-
157
- return {
158
- generalErrors,
159
- errors
160
- }
161
- },
162
- render({ errors, generalErrors }: any) {
163
- return h(WrappedComponent, {
164
- errors,
165
- generalErrors,
166
- ...this.$attrs
167
- } as any, this.$slots)
168
- }
169
- }
170
- }
171
- }
172
-
173
- export type Policies = "local" | "session" | "querystring"
174
- export type defaultValuesPriorityUnion = "tanstack" | "persistency" | "schema"
175
-
176
- const includesPolicy = (arr: Policies[], policy: Policies) => {
177
- return arr.includes(policy)
178
- }
179
-
180
- export type OmegaConfig<T> = {
181
- i18nNamespace?: string
182
-
183
- persistency?: {
184
- /** Order of importance:
185
- * - "querystring": Highest priority when persisting
186
- * - "local" and then "session": Lower priority storage options
187
- */
188
- policies?: UnionToTuples<Policies>
189
- overrideDefaultValues?: "deprecated: use defaultValuesPriority"
190
- id?: string
191
- } & keysRule<T>
192
-
193
- ignorePreventCloseEvents?: boolean
194
-
195
- /**
196
- * Prevents browser window/tab exit when form has unsaved changes.
197
- * Shows native browser "Leave site?" dialog.
198
- *
199
- * @remarks
200
- * - Opt-in only: Must explicitly enable
201
- * - Independent from data persistence feature
202
- */
203
- preventWindowExit?: "prevent" | "prevent-and-reset" | "nope"
204
-
205
- input?: any
206
-
207
- /**
208
- * Default values order is: Tanstack default values passed as second parameter to useOmegaForm, then persistency
209
- * default values from querystring or local/session storage, then defaults from schema
210
- * You can customize the order and with omegaConfig.defaultValuesPriority
211
- * default value = ['tanstack', 'persistency', 'schema']
212
- */
213
- defaultValuesPriority?: UnionToTuples<defaultValuesPriorityUnion>
214
-
215
- defaultFromSchema?: "deprecated: use defaultValuesPriority"
216
- }
24
+ import { makeRunPromise } from "@effect-app/vue/runtime"
25
+ import { useIntl } from "../../utils"
217
26
 
218
- export interface OF<From, To> extends OmegaFormApi<From, To> {
219
- meta: MetaRecord<From>
220
- unionMeta: Record<string, MetaRecord<From>>
221
- clear: () => void
222
- i18nNamespace?: string
223
- ignorePreventCloseEvents?: boolean
224
- registerField: (
225
- field: ComputedRef<{
226
- name: string
227
- label: string
228
- id: string
229
- }>
230
- ) => void
231
- /** @experimental */
232
- handleSubmitEffect: {
233
- /**
234
- * when `checkErrors` is true, the Effect will fail with `FormErrors<From>` when there are validation errors
235
- * @experimental */
236
- (options: { checkErrors: true; meta?: Record<string, any> }): Effect.Effect<void, FormErrors<From>>
237
- /** @experimental */
238
- (options?: { meta?: Record<string, any> }): Effect.Effect<void>
239
- }
240
- }
27
+ export { useErrorLabel } from "./errors"
28
+ export { FormErrors } from "./submit"
29
+ export type { defaultValuesPriorityUnion, OF, OmegaConfig, OmegaFormReturn, Policies } from "./types"
241
30
 
242
31
  export const OmegaFormKey = Symbol("OmegaForm") as InjectionKey<OF<any, any>>
243
32
 
244
- type __VLS_PrettifyLocal<T> =
245
- & {
246
- [K in keyof T]: T[K]
247
- }
248
- & {}
249
-
250
- // Type aliases for Array component slots - using cached types for performance
251
- type CachedFieldApi<From, To, TypeProps = DefaultTypeProps> = import("@tanstack/vue-form").FieldApi<
252
- From,
253
- OmegaFormReturn<From, To, TypeProps>["_keys"],
254
- DeepValue<From, OmegaFormReturn<From, To, TypeProps>["_keys"]>,
255
- | import("@tanstack/vue-form").FieldValidateOrFn<
256
- From,
257
- OmegaFormReturn<From, To, TypeProps>["_keys"],
258
- DeepValue<From, OmegaFormReturn<From, To, TypeProps>["_keys"]>
259
- >
260
- | undefined,
261
- | import("@tanstack/vue-form").FieldValidateOrFn<
262
- From,
263
- OmegaFormReturn<From, To, TypeProps>["_keys"],
264
- DeepValue<From, OmegaFormReturn<From, To, TypeProps>["_keys"]>
265
- >
266
- | undefined,
267
- | import("@tanstack/vue-form").FieldAsyncValidateOrFn<
268
- From,
269
- OmegaFormReturn<From, To, TypeProps>["_keys"],
270
- DeepValue<From, OmegaFormReturn<From, To, TypeProps>["_keys"]>
271
- >
272
- | undefined,
273
- | import("@tanstack/vue-form").FieldValidateOrFn<
274
- From,
275
- OmegaFormReturn<From, To, TypeProps>["_keys"],
276
- DeepValue<From, OmegaFormReturn<From, To, TypeProps>["_keys"]>
277
- >
278
- | undefined,
279
- | import("@tanstack/vue-form").FieldAsyncValidateOrFn<
280
- From,
281
- OmegaFormReturn<From, To, TypeProps>["_keys"],
282
- DeepValue<From, OmegaFormReturn<From, To, TypeProps>["_keys"]>
283
- >
284
- | undefined,
285
- | import("@tanstack/vue-form").FieldValidateOrFn<
286
- From,
287
- OmegaFormReturn<From, To, TypeProps>["_keys"],
288
- DeepValue<From, OmegaFormReturn<From, To, TypeProps>["_keys"]>
289
- >
290
- | undefined,
291
- | import("@tanstack/vue-form").FieldAsyncValidateOrFn<
292
- From,
293
- OmegaFormReturn<From, To, TypeProps>["_keys"],
294
- DeepValue<From, OmegaFormReturn<From, To, TypeProps>["_keys"]>
295
- >
296
- | undefined,
297
- | import("@tanstack/vue-form").FieldValidateOrFn<
298
- From,
299
- OmegaFormReturn<From, To, TypeProps>["_keys"],
300
- DeepValue<From, OmegaFormReturn<From, To, TypeProps>["_keys"]>
301
- >
302
- | undefined,
303
- | import("@tanstack/vue-form").FieldAsyncValidateOrFn<
304
- From,
305
- OmegaFormReturn<From, To, TypeProps>["_keys"],
306
- DeepValue<From, OmegaFormReturn<From, To, TypeProps>["_keys"]>
307
- >
308
- | undefined,
309
- import("@tanstack/vue-form").FormValidateOrFn<From> | undefined,
310
- import("@tanstack/vue-form").FormValidateOrFn<From> | undefined,
311
- import("@tanstack/vue-form").StandardSchemaV1<From, To>,
312
- import("@tanstack/vue-form").FormValidateOrFn<From> | undefined,
313
- import("@tanstack/vue-form").FormAsyncValidateOrFn<From> | undefined,
314
- import("@tanstack/vue-form").FormValidateOrFn<From> | undefined,
315
- import("@tanstack/vue-form").FormAsyncValidateOrFn<From> | undefined,
316
- import("@tanstack/vue-form").FormValidateOrFn<From> | undefined,
317
- import("@tanstack/vue-form").FormAsyncValidateOrFn<From> | undefined,
318
- import("@tanstack/vue-form").FormAsyncValidateOrFn<From> | undefined,
319
- Record<string, any> | undefined
320
- >
321
-
322
- type CachedFieldState<From, To, TypeProps = DefaultTypeProps> = import("@tanstack/vue-form").FieldState<
323
- From,
324
- OmegaFormReturn<From, To, TypeProps>["_keys"],
325
- DeepValue<From, OmegaFormReturn<From, To, TypeProps>["_keys"]>,
326
- | import("@tanstack/vue-form").FieldValidateOrFn<
327
- From,
328
- OmegaFormReturn<From, To, TypeProps>["_keys"],
329
- DeepValue<From, OmegaFormReturn<From, To, TypeProps>["_keys"]>
330
- >
331
- | undefined,
332
- | import("@tanstack/vue-form").FieldValidateOrFn<
333
- From,
334
- OmegaFormReturn<From, To, TypeProps>["_keys"],
335
- DeepValue<From, OmegaFormReturn<From, To, TypeProps>["_keys"]>
336
- >
337
- | undefined,
338
- | import("@tanstack/vue-form").FieldAsyncValidateOrFn<
339
- From,
340
- OmegaFormReturn<From, To, TypeProps>["_keys"],
341
- DeepValue<From, OmegaFormReturn<From, To, TypeProps>["_keys"]>
342
- >
343
- | undefined,
344
- | import("@tanstack/vue-form").FieldValidateOrFn<
345
- From,
346
- OmegaFormReturn<From, To, TypeProps>["_keys"],
347
- DeepValue<From, OmegaFormReturn<From, To, TypeProps>["_keys"]>
348
- >
349
- | undefined,
350
- | import("@tanstack/vue-form").FieldAsyncValidateOrFn<
351
- From,
352
- OmegaFormReturn<From, To, TypeProps>["_keys"],
353
- DeepValue<From, OmegaFormReturn<From, To, TypeProps>["_keys"]>
354
- >
355
- | undefined,
356
- | import("@tanstack/vue-form").FieldValidateOrFn<
357
- From,
358
- OmegaFormReturn<From, To, TypeProps>["_keys"],
359
- DeepValue<From, OmegaFormReturn<From, To, TypeProps>["_keys"]>
360
- >
361
- | undefined,
362
- | import("@tanstack/vue-form").FieldAsyncValidateOrFn<
363
- From,
364
- OmegaFormReturn<From, To, TypeProps>["_keys"],
365
- DeepValue<From, OmegaFormReturn<From, To, TypeProps>["_keys"]>
366
- >
367
- | undefined,
368
- | import("@tanstack/vue-form").FieldValidateOrFn<
369
- From,
370
- OmegaFormReturn<From, To, TypeProps>["_keys"],
371
- DeepValue<From, OmegaFormReturn<From, To, TypeProps>["_keys"]>
372
- >
373
- | undefined,
374
- | import("@tanstack/vue-form").FieldAsyncValidateOrFn<
375
- From,
376
- OmegaFormReturn<From, To, TypeProps>["_keys"],
377
- DeepValue<From, OmegaFormReturn<From, To, TypeProps>["_keys"]>
378
- >
379
- | undefined,
380
- import("@tanstack/vue-form").FormValidateOrFn<From> | undefined,
381
- import("@tanstack/vue-form").FormValidateOrFn<From> | undefined,
382
- import("@tanstack/vue-form").StandardSchemaV1<From, To>,
383
- import("@tanstack/vue-form").FormValidateOrFn<From> | undefined,
384
- import("@tanstack/vue-form").FormAsyncValidateOrFn<From> | undefined,
385
- import("@tanstack/vue-form").FormValidateOrFn<From> | undefined,
386
- import("@tanstack/vue-form").FormAsyncValidateOrFn<From> | undefined,
387
- import("@tanstack/vue-form").FormValidateOrFn<From> | undefined,
388
- import("@tanstack/vue-form").FormAsyncValidateOrFn<From> | undefined
389
- >
390
-
391
- export interface OmegaFormReturn<
392
- From extends Record<PropertyKey, any>,
393
- To extends Record<PropertyKey, any>,
394
- TypeProps = DefaultTypeProps
395
- > extends OF<From, To> {
396
- // Pre-computed type aliases - computed ONCE for performance
397
- _paths: FieldPath<From>
398
- _keys: DeepKeys<From>
399
- _schema: S.Codec<To, From, never>
400
-
401
- // this crazy thing here is copied from the OmegaFormInput.vue.d.ts, with `From` removed as Generic, instead closed over from the From generic above..
402
- Input: <Name extends OmegaFormReturn<From, To, TypeProps>["_paths"]>(
403
- __VLS_props: NonNullable<Awaited<typeof __VLS_setup>>["props"],
404
- __VLS_ctx?: __VLS_PrettifyLocal<Pick<NonNullable<Awaited<typeof __VLS_setup>>, "attrs" | "emit" | "slots">>,
405
- __VLS_expose?: NonNullable<Awaited<typeof __VLS_setup>>["expose"],
406
- __VLS_setup?: Promise<{
407
- props:
408
- & __VLS_PrettifyLocal<
409
- & Pick<
410
- & Partial<{}>
411
- & Omit<
412
- {} & import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps,
413
- never
414
- >,
415
- never
416
- >
417
- & TypeProps
418
- & Partial<{}>
419
- >
420
- & BaseProps<From, Name>
421
- & import("vue").PublicProps
422
- expose(exposed: import("vue").ShallowUnwrapRef<{}>): void
423
- attrs: any
424
- slots: {
425
- default?(props: MergedInputProps<From, Name>): void
426
- label?: (props: { required: boolean; id: string; label: string }) => void
427
- }
428
- emit: {}
429
- }>
430
- ) => import("vue").VNode & {
431
- __ctx?: Awaited<typeof __VLS_setup>
432
- }
433
- Errors: (
434
- __VLS_props: NonNullable<Awaited<typeof __VLS_setup>>["props"],
435
- __VLS_ctx?: __VLS_PrettifyLocal<Pick<NonNullable<Awaited<typeof __VLS_setup>>, "attrs" | "emit" | "slots">>,
436
- __VLS_expose?: NonNullable<Awaited<typeof __VLS_setup>>["expose"],
437
- __VLS_setup?: Promise<{
438
- props:
439
- & __VLS_PrettifyLocal<
440
- & Pick<
441
- & Partial<{}>
442
- & Omit<
443
- {} & import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps,
444
- never
445
- >,
446
- never
447
- >
448
- & Partial<{}>
449
- >
450
- & import("vue").PublicProps
451
- expose(exposed: import("vue").ShallowUnwrapRef<{}>): void
452
- attrs: any
453
- slots: {
454
- default: (props: { errors: readonly OmegaError[]; showedGeneralErrors: string[] }) => void
455
- }
456
- emit: {}
457
- }>
458
- ) => import("vue").VNode & {
459
- __ctx?: Awaited<typeof __VLS_setup>
460
- }
461
- TaggedUnion: <Name extends OmegaFormReturn<From, To, TypeProps>["_keys"]>(
462
- __VLS_props: NonNullable<Awaited<typeof __VLS_setup>>["props"],
463
- __VLS_ctx?: __VLS_PrettifyLocal<Pick<NonNullable<Awaited<typeof __VLS_setup>>, "attrs" | "emit" | "slots">>,
464
- __VLS_expose?: NonNullable<Awaited<typeof __VLS_setup>>["expose"],
465
- __VLS_setup?: Promise<{
466
- props:
467
- & __VLS_PrettifyLocal<
468
- & Pick<
469
- & Partial<{}>
470
- & Omit<
471
- {} & import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps,
472
- never
473
- >,
474
- never
475
- >
476
- & {
477
- name?: Name
478
- type?: "select" | "radio"
479
- options: import("./InputProps").TaggedUnionOptionsArray<From, Name>
480
- _debugName?: [NoInfer<Name>]
481
- label?: string
482
- }
483
- & {}
484
- >
485
- & import("vue").PublicProps
486
- expose(exposed: import("vue").ShallowUnwrapRef<{}>): void
487
- attrs: any
488
- slots: Record<
489
- string,
490
- (props: {
491
- field: import("@tanstack/vue-form").FieldApi<
492
- From,
493
- Name,
494
- DeepValue<From, Name>,
495
- any,
496
- any,
497
- any,
498
- any,
499
- any,
500
- any,
501
- any,
502
- any,
503
- any,
504
- any,
505
- any,
506
- any,
507
- any,
508
- any,
509
- any,
510
- any,
511
- any,
512
- any,
513
- any,
514
- any
515
- >
516
- state: import("@tanstack/vue-form").FieldState<
517
- From,
518
- Name,
519
- DeepValue<From, Name>,
520
- any,
521
- any,
522
- any,
523
- any,
524
- any,
525
- any,
526
- any,
527
- any,
528
- any,
529
- any,
530
- any,
531
- any,
532
- any,
533
- any,
534
- any,
535
- any,
536
- any,
537
- any
538
- >
539
- }) => any
540
- >
541
- emit: {}
542
- }>
543
- ) => import("vue").VNode & {
544
- __ctx?: Awaited<typeof __VLS_setup>
545
- }
546
- Array: <Name extends OmegaFormReturn<From, To, TypeProps>["_keys"]>(
547
- __VLS_props: NonNullable<Awaited<typeof __VLS_setup>>["props"],
548
- __VLS_ctx?: __VLS_PrettifyLocal<Pick<NonNullable<Awaited<typeof __VLS_setup>>, "attrs" | "emit" | "slots">>,
549
- __VLS_expose?: NonNullable<Awaited<typeof __VLS_setup>>["expose"],
550
- __VLS_setup?: Promise<{
551
- props:
552
- & __VLS_PrettifyLocal<
553
- & Pick<
554
- & Partial<{}>
555
- & Omit<
556
- {} & import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps,
557
- never
558
- >,
559
- never
560
- >
561
- & (Omit<OmegaArrayProps<From, To, Name>, "form">)
562
- & {}
563
- >
564
- & import("vue").PublicProps
565
- expose(exposed: import("vue").ShallowUnwrapRef<{}>): void
566
- attrs: any
567
- slots: {
568
- "pre-array"?: (props: {
569
- field: CachedFieldApi<From, To, TypeProps>
570
- state: CachedFieldState<From, To, TypeProps>
571
- }) => any
572
- } & {
573
- default?: (props: {
574
- subField: CachedFieldApi<From, To, TypeProps>
575
- subState: CachedFieldState<From, To, TypeProps>
576
- index: number
577
- field: CachedFieldApi<From, To, TypeProps>
578
- }) => any
579
- } & {
580
- "post-array"?: (props: {
581
- field: CachedFieldApi<From, To, TypeProps>
582
- state: CachedFieldState<From, To, TypeProps>
583
- }) => any
584
- } & {
585
- field?: (props: {
586
- field: CachedFieldApi<From, To, TypeProps>
587
- }) => any
588
- }
589
- emit: {}
590
- }>
591
- ) => import("vue").VNode & {
592
- __ctx?: Awaited<typeof __VLS_setup>
593
- }
594
-
595
- AutoGen: <Name extends OmegaFormReturn<From, To, TypeProps>["_keys"]>(
596
- __VLS_props: NonNullable<Awaited<typeof __VLS_setup>>["props"],
597
- __VLS_ctx?: __VLS_PrettifyLocal<Pick<NonNullable<Awaited<typeof __VLS_setup>>, "attrs" | "emit" | "slots">>,
598
- __VLS_expose?: NonNullable<Awaited<typeof __VLS_setup>>["expose"],
599
- __VLS_setup?: Promise<{
600
- props:
601
- & __VLS_PrettifyLocal<
602
- Pick<
603
- & Partial<{}>
604
- & Omit<
605
- {} & import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps,
606
- never
607
- >,
608
- never
609
- > & {
610
- // form: OmegaInputProps<From, To>["form"]
611
- pick?: OmegaFormReturn<From, To, TypeProps>["_keys"][]
612
- omit?: OmegaFormReturn<From, To, TypeProps>["_keys"][]
613
- labelMap?: (key: OmegaFormReturn<From, To, TypeProps>["_keys"]) => string | undefined
614
- filterMap?: <M extends OmegaAutoGenMeta<From, To, Name>>(
615
- key: OmegaFormReturn<From, To, TypeProps>["_keys"],
616
- meta: M
617
- ) => boolean | M
618
- order?: OmegaFormReturn<From, To, TypeProps>["_keys"][]
619
- sort?: Order.Order<OmegaAutoGenMeta<From, To, Name>>
620
- } & {}
621
- >
622
- & import("vue").PublicProps
623
- expose(exposed: import("vue").ShallowUnwrapRef<{}>): void
624
- attrs: any
625
- slots: {
626
- default(props: {
627
- child: OmegaAutoGenMeta<From, To, Name>
628
- }): void
629
- }
630
- emit: {}
631
- }>
632
- ) => import("vue").VNode & {
633
- __ctx?: Awaited<typeof __VLS_setup>
634
- }
635
-
636
- Form: <K extends keyof OmegaFormState<To, From>>(
637
- __VLS_props: NonNullable<Awaited<typeof __VLS_setup>>["props"],
638
- __VLS_ctx?: __VLS_PrettifyLocal<Pick<NonNullable<Awaited<typeof __VLS_setup>>, "attrs" | "emit" | "slots">>,
639
- __VLS_expose?: NonNullable<Awaited<typeof __VLS_setup>>["expose"],
640
- __VLS_setup?: Promise<{
641
- props:
642
- & __VLS_PrettifyLocal<
643
- Pick<
644
- & Partial<{}>
645
- & Omit<
646
- {} & import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps,
647
- never
648
- >,
649
- never
650
- > & {
651
- // form: OmegaFormReturn<From, To, Props>
652
- disabled?: boolean
653
- subscribe?: K[]
654
- } & {}
655
- >
656
- & import("vue").PublicProps
657
- expose(exposed: import("vue").ShallowUnwrapRef<{}>): void
658
- attrs: any
659
- slots: {
660
- default(props: {
661
- subscribedValues: K[] extends undefined[] ? Record<string, never> : Pick<OmegaFormState<From, To>, K>
662
- }): void
663
- }
664
- emit: {}
665
- }>
666
- ) => import("vue").VNode & {
667
- __ctx?: Awaited<typeof __VLS_setup>
668
- }
669
- }
33
+ const runPromise = makeRunPromise(Context.empty())
670
34
 
671
35
  export const useOmegaForm = <
672
36
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -675,98 +39,59 @@ export const useOmegaForm = <
675
39
  To extends Record<PropertyKey, any>,
676
40
  TypeProps = DefaultTypeProps
677
41
  >(
678
- schema: S.Codec<To, From, never>,
42
+ schema: S.Codec<To, From>,
679
43
  tanstackFormOptions?: NoInfer<FormProps<From, To>>,
680
44
  omegaConfig?: OmegaConfig<To>
681
45
  ): OmegaFormReturn<From, To, TypeProps> => {
682
46
  if (!schema) throw new Error("Schema is required")
683
- const standardSchema = S.toStandardSchemaV1(schema)
684
- const decode = S.decodeUnknownEffect(schema)
685
-
686
- const { meta, unionMeta } = generateMetaFromSchema(schema)
687
-
688
- const persistencyKey = computed(() => {
689
- if (omegaConfig?.persistency?.id) {
690
- return omegaConfig.persistency.id
691
- }
692
- const path = window.location.pathname
693
- const keys = Object.keys(meta)
694
- return `${path}-${keys.join("-")}`
695
- })
696
-
697
- const clearUrlParams = () => {
698
- const params = new URLSearchParams(window.location.search)
699
- params.delete(persistencyKey.value)
700
- const url = new URL(window.location.href)
701
- url.search = params.toString()
702
- window.history.replaceState({}, "", url.toString())
703
- }
704
-
705
- const defaultValues = computed(() => {
706
- // will contain what we get from querystring or local/session storage
707
- let persistencyDefaultValues
708
-
709
- const persistency = omegaConfig?.persistency
710
-
711
- if (
712
- // query string has higher priority than local/session storage
713
- persistency?.policies
714
- && !persistencyDefaultValues
715
- && (includesPolicy(persistency.policies, "local")
716
- || includesPolicy(persistency.policies, "session"))
717
- ) {
718
- const storage = includesPolicy(persistency.policies, "local")
719
- ? localStorage
720
- : sessionStorage
721
- if (storage) {
722
- try {
723
- const value = JSON.parse(
724
- storage.getItem(persistencyKey.value) || "{}"
725
- )
726
- storage.removeItem(persistencyKey.value)
727
- persistencyDefaultValues = value
728
- } catch (error) {
729
- console.error(error)
730
- }
731
- }
732
- }
733
- if (persistency?.policies && includesPolicy(persistency.policies, "querystring")) {
734
- try {
735
- const params = new URLSearchParams(window.location.search)
736
- const value = params.get(persistencyKey.value)
737
- clearUrlParams()
738
- if (value) {
739
- persistencyDefaultValues = deepMerge(persistencyDefaultValues || {}, JSON.parse(value))
740
- }
741
- } catch (error) {
742
- console.error(error)
743
- }
744
- }
745
-
746
- // to be sure we have a valid object at the end of the gathering process
747
- persistencyDefaultValues ??= {}
748
-
749
- const defaults: Record<defaultValuesPriorityUnion, any> = {
750
- tanstack: tanstackFormOptions?.defaultValues || {},
751
- persistency: persistencyDefaultValues,
752
- schema: defaultsValueFromSchema(schema)
753
- }
754
-
755
- return (omegaConfig?.defaultValuesPriority || ["tanstack", "persistency", "schema"] as const).reverse().reduce(
756
- (acc, m) => {
757
- if (!Object.keys(acc).length) {
758
- return defaults[m]
759
- }
760
- return deepMerge(acc, defaults[m])
761
- },
762
- {}
763
- )
47
+ const { trans } = useIntl()
48
+ const formCompatibleSchema = toFormSchema(schema)
49
+ // Effect's Standard Schema formatter emits `Expected X | Y, got Z` for
50
+ // `AnyOf` issues without consulting our hooks. Pre-annotate literal-union
51
+ // (select) and literal-array (multiple) AST nodes with a localized
52
+ // `message` so the formatter picks them up via `findMessage`.
53
+ const localizedSchema = annotateLiteralUnionMessages(formCompatibleSchema, trans)
54
+
55
+ // A nullable struct (`S.NullOr(S.Struct(...))`) has no slot in the form
56
+ // value until a child is filled. Once it materialises, its untouched
57
+ // children are still `undefined` — which strict `S.NullOr(...)` children
58
+ // reject. Deep-fill those children with their defaults before both
59
+ // validation and decoding so they validate as `null` (or their default).
60
+ const formAst = unwrapDeclaration(formCompatibleSchema.ast)
61
+ const normalizeFormValue = (value: unknown) => fillNestedDefaults(formAst, value)
62
+
63
+ const baseStandardSchema = toLocalizedStandardSchemaV1(
64
+ localizedSchema as any,
65
+ trans
66
+ )
67
+ const standardSchema: typeof baseStandardSchema = {
68
+ ...baseStandardSchema,
69
+ "~standard": {
70
+ ...baseStandardSchema["~standard"],
71
+ validate: (value) => baseStandardSchema["~standard"].validate(normalizeFormValue(value))
72
+ }
73
+ }
74
+
75
+ const baseDecode = S.decodeUnknownEffectConcurrently(formCompatibleSchema)
76
+ const decode = (value: From) => baseDecode(normalizeFormValue(value))
77
+
78
+ const { meta, unionMeta } = generateMetaFromSchema(formCompatibleSchema)
79
+
80
+ // Persistency must be created before `useForm` so its merged
81
+ // `defaultValues` (tanstack + storage/querystring + schema) can flow into
82
+ // the form. The `getForm` accessor is lazy because the form is constructed
83
+ // immediately after, and persistency's listeners only fire later.
84
+ const formHolder: { form: any } = { form: undefined }
85
+ const persistency = usePersistency<From>({
86
+ meta,
87
+ persistency: omegaConfig?.persistency,
88
+ preventWindowExit: omegaConfig?.preventWindowExit,
89
+ defaultValuesPriority: omegaConfig?.defaultValuesPriority,
90
+ tanstackDefaultValues: tanstackFormOptions?.defaultValues,
91
+ schemaDefaultValues: () => defaultsValueFromSchema(schema),
92
+ getForm: () => formHolder.form
764
93
  })
765
94
 
766
- const wrapWithSpan = (span: api.Span | undefined, toWrap: () => any) => {
767
- return span ? api.context.with(api.trace.setSpan(api.context.active(), span), toWrap) : toWrap()
768
- }
769
-
770
95
  const form = useForm<
771
96
  From,
772
97
  FormValidateOrFn<From> | undefined,
@@ -782,34 +107,37 @@ export const useOmegaForm = <
782
107
  Record<string, any> | undefined
783
108
  >({
784
109
  ...tanstackFormOptions,
110
+ validationLogic: revalidateLogic(),
785
111
  validators: {
786
- onSubmit: standardSchema,
787
- ...(tanstackFormOptions?.validators || {})
112
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
113
+ onDynamic: standardSchema as any,
114
+ ...tanstackFormOptions?.validators
788
115
  },
789
- onSubmit: tanstackFormOptions?.onSubmit
790
- ? ({ formApi, meta, value }) =>
791
- wrapWithSpan(meta?.currentSpan, async () => {
792
- // validators only validate, they don't actually transform, so we have to do that manually here.
793
- const parsedValue = await Effect.runPromise(decode(value))
794
- const r = tanstackFormOptions.onSubmit!({
795
- formApi: formApi as OmegaFormApi<From, To>,
796
- meta,
797
- value: parsedValue
798
- })
799
- if (Fiber.isFiber(r)) {
800
- return await runtimeFiberAsPromise(r)
801
- }
802
- if (Effect.isEffect(r)) {
803
- const effectResult = await Effect.runPromise(r)
804
- return Fiber.isFiber(effectResult)
805
- ? await runtimeFiberAsPromise(effectResult)
806
- : effectResult
807
- }
808
- return r
809
- })
810
- : undefined,
811
- defaultValues: defaultValues.value as any
116
+ onSubmit: wrapOnSubmit<From, To>(tanstackFormOptions?.onSubmit, decode, runPromise),
117
+ defaultValues: persistency.defaultValues.value
812
118
  }) satisfies OmegaFormApi<To, From>
119
+ formHolder.form = form
120
+
121
+ // Keep the live form state consistent with the schema: when a nullable
122
+ // struct materialises (a child got a value), backfill its untouched
123
+ // children so `values` reflects what is validated and submitted. Called
124
+ // from the field change handler — the only point a struct can materialise.
125
+ const normalizeNullableStructs = () => {
126
+ const current = form.state.values
127
+ const filled = fillNestedDefaults(formAst, current)
128
+ if (filled === current || !filled || typeof filled !== "object") return
129
+ const currentRecord = current as Partial<Record<Extract<keyof From, string>, unknown>>
130
+ const filledRecord = filled as Partial<Record<Extract<keyof From, string>, unknown>>
131
+ for (const key of Object.keys(filledRecord) as Array<Extract<keyof From, string>>) {
132
+ if (filledRecord[key] !== currentRecord[key]) {
133
+ const field = key as DeepKeys<From>
134
+ form.setFieldValue(field, filledRecord[key] as DeepValue<From, typeof field>, {
135
+ dontUpdateMeta: true,
136
+ dontValidate: true
137
+ })
138
+ }
139
+ }
140
+ }
813
141
 
814
142
  const clear = () => {
815
143
  Object.keys(meta).forEach((key: any) => {
@@ -817,89 +145,6 @@ export const useOmegaForm = <
817
145
  })
818
146
  }
819
147
 
820
- const createNestedObjectFromPaths = (paths: string[]) =>
821
- paths.reduce((result, path) => {
822
- const parts = path.split(".")
823
- parts.reduce((acc, part, i) => {
824
- if (i === parts.length - 1) {
825
- acc[part] = form.getFieldValue(path as any)
826
- } else {
827
- acc[part] = acc[part] ?? {}
828
- }
829
- return acc[part]
830
- }, result)
831
- return result
832
- }, {} as Record<string, any>)
833
-
834
- const persistFilter = (persistency: OmegaConfig<From>["persistency"]) => {
835
- if (!persistency) return
836
- const { banKeys, keys } = persistency
837
- if (Array.isArray(keys)) {
838
- return createNestedObjectFromPaths(keys as string[])
839
- }
840
- if (Array.isArray(banKeys)) {
841
- const subs = Object.keys(meta).filter((metakey) => banKeys.includes(metakey as any))
842
- return createNestedObjectFromPaths(subs)
843
- }
844
- return form.store.state.values
845
- }
846
-
847
- const persistData = () => {
848
- const persistency = omegaConfig?.persistency
849
- if (!persistency?.policies || persistency.policies.length === 0) {
850
- return
851
- }
852
- if (
853
- includesPolicy(persistency.policies, "local")
854
- || includesPolicy(persistency.policies, "session")
855
- ) {
856
- const storage = includesPolicy(persistency.policies, "local")
857
- ? localStorage
858
- : sessionStorage
859
- if (!storage) return
860
- const values = persistFilter(persistency)
861
- return storage.setItem(persistencyKey.value, JSON.stringify(values))
862
- }
863
- }
864
-
865
- const saveDataInUrl = () => {
866
- const persistency = omegaConfig?.persistency
867
- if (!persistency?.policies || persistency.policies.length === 0) {
868
- return
869
- }
870
- if (includesPolicy(persistency.policies, "querystring")) {
871
- const values = persistFilter(persistency)
872
- const searchParams = new URLSearchParams(window.location.search)
873
- searchParams.set(persistencyKey.value, JSON.stringify(values))
874
- const url = new URL(window.location.href)
875
- url.search = searchParams.toString()
876
- window.history.replaceState({}, "", url.toString())
877
- }
878
- }
879
-
880
- const preventWindowExit = (e: BeforeUnloadEvent) => {
881
- if (form.store.state.isDirty) {
882
- e.preventDefault()
883
- }
884
- }
885
-
886
- onUnmounted(persistData)
887
-
888
- onMounted(() => {
889
- window.addEventListener("beforeunload", persistData)
890
- window.addEventListener("blur", saveDataInUrl)
891
- if (omegaConfig?.preventWindowExit && omegaConfig.preventWindowExit !== "nope") {
892
- window.addEventListener("beforeunload", preventWindowExit)
893
- }
894
- })
895
- onBeforeUnmount(() => {
896
- window.removeEventListener("beforeunload", persistData)
897
- window.removeEventListener("blur", saveDataInUrl)
898
- if (omegaConfig?.preventWindowExit && omegaConfig.preventWindowExit !== "nope") {
899
- window.removeEventListener("beforeunload", preventWindowExit)
900
- }
901
- })
902
-
903
148
  // Watch for successful form submissions and auto-reset if prevent-and-reset is enabled
904
149
  // We put it as a side effect, so we don't overwhelm submit handler and we can support
905
150
  // effects submission more freely
@@ -918,33 +163,9 @@ export const useOmegaForm = <
918
163
  })
919
164
  }
920
165
 
921
- const handleSubmitEffect_ = (meta?: Record<string, any>) =>
922
- Effect.currentSpan.pipe(
923
- Effect.option,
924
- Effect
925
- .flatMap((span) =>
926
- Effect.promise(() => form.handleSubmit(Option.isSome(span) ? { currentSpan: span.value, ...meta } : meta))
927
- )
928
- )
166
+ const { handleSubmit, handleSubmitEffect } = makeSubmitHandlers<From, To>(form)
929
167
 
930
- const handleSubmitEffect: {
931
- (options: { checkErrors: true; meta?: Record<string, any> }): Effect.Effect<void, FormErrors<From>>
932
- (options?: { meta?: Record<string, any> }): Effect.Effect<void>
933
- } = (
934
- options?: { meta?: Record<string, any>; checkErrors?: true }
935
- ): any =>
936
- options?.checkErrors
937
- ? handleSubmitEffect_(options?.meta).pipe(Effect.flatMap(Effect.fnUntraced(function*() {
938
- const errors = form.getAllErrors()
939
- if (Object.keys(errors.fields).length || errors.form.errors.length) {
940
- return yield* Effect.fail(new FormErrors({ form: errors.form, fields: errors.fields }))
941
- }
942
- })))
943
- : handleSubmitEffect_(options?.meta)
944
-
945
- const handleSubmit = form.handleSubmit
946
-
947
- const fieldMap = ref(new Map<string, { label: string; id: string }>())
168
+ const { fieldMap, registerField } = makeFieldMap()
948
169
 
949
170
  const formWithExtras: OF<From, To> = Object.assign(form, {
950
171
  i18nNamespace: omegaConfig?.i18nNamespace,
@@ -952,26 +173,11 @@ export const useOmegaForm = <
952
173
  meta,
953
174
  unionMeta,
954
175
  clear,
955
- handleSubmit: (meta?: Record<string, any>) => {
956
- const span = api.trace.getSpan(api.context.active())
957
- return handleSubmit({ currentSpan: span, ...meta })
958
- },
176
+ normalizeNullableStructs,
177
+ handleSubmit,
959
178
  // /** @experimental */
960
179
  handleSubmitEffect,
961
- registerField: (field: ComputedRef<{ name: string; label: string; id: string }>) => {
962
- watch(field, (f) => {
963
- fieldMap.value.set(f.name, { label: f.label, id: f.id })
964
- }, { immediate: true })
965
- onUnmounted(() => {
966
- // Only delete if we still own this entry (id matches)
967
- // This prevents old components from deleting entries registered by new components
968
- // during re-mount transitions (e.g., when :key changes)
969
- const currentEntry = fieldMap.value.get(field.value.name)
970
- if (currentEntry?.id === field.value.id) {
971
- fieldMap.value.delete(field.value.name)
972
- }
973
- })
974
- }
180
+ registerField
975
181
  })
976
182
 
977
183
  const errorContext = { form: formWithExtras, fieldMap }