@effect-app/vue-components 4.0.0-beta.3 → 4.0.0-beta.300

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