@effect-app/vue-components 4.0.0-beta.158 → 4.0.0-beta.159

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 (124) hide show
  1. package/dist/types/components/OmegaForm/OmegaArray.vue.d.ts +1 -1
  2. package/dist/types/components/OmegaForm/OmegaAutoGen.vue.d.ts +1 -1
  3. package/dist/types/components/OmegaForm/OmegaErrorsInternal.vue.d.ts +1 -1
  4. package/dist/types/components/OmegaForm/OmegaFormInput.vue.d.ts +1 -1
  5. package/dist/types/components/OmegaForm/OmegaInput.vue.d.ts +1 -1
  6. package/dist/types/components/OmegaForm/OmegaInternalInput.vue.d.ts +2 -1
  7. package/dist/types/components/OmegaForm/OmegaWrapper.vue.d.ts +1 -1
  8. package/dist/types/components/OmegaForm/createUseFormWithCustomInput.d.ts +2 -2
  9. package/dist/types/components/OmegaForm/errors.d.ts +33 -0
  10. package/dist/types/components/OmegaForm/getOmegaStore.d.ts +1 -1
  11. package/dist/types/components/OmegaForm/hocs.d.ts +3 -0
  12. package/dist/types/components/OmegaForm/index.d.ts +13 -3
  13. package/dist/types/components/OmegaForm/inputs.d.ts +4 -0
  14. package/dist/types/components/OmegaForm/meta/checks.d.ts +4 -0
  15. package/dist/types/components/OmegaForm/meta/createMeta.d.ts +32 -0
  16. package/dist/types/components/OmegaForm/meta/defaults.d.ts +2 -0
  17. package/dist/types/components/OmegaForm/meta/redacted.d.ts +2 -0
  18. package/dist/types/components/OmegaForm/meta/types.d.ts +56 -0
  19. package/dist/types/components/OmegaForm/meta/walker.d.ts +18 -0
  20. package/dist/types/components/OmegaForm/persistency.d.ts +58 -0
  21. package/dist/types/components/OmegaForm/submit.d.ts +60 -0
  22. package/dist/types/components/OmegaForm/types.d.ts +281 -0
  23. package/dist/types/components/OmegaForm/useOmegaForm.d.ts +6 -212
  24. package/dist/types/components/OmegaForm/validation/localized.d.ts +10 -0
  25. package/dist/vue-components.es.js +24 -16
  26. package/dist/vue-components10.es.js +4 -4
  27. package/dist/vue-components11.es.js +19 -12
  28. package/dist/vue-components12.es.js +22 -444
  29. package/dist/vue-components13.es.js +126 -3
  30. package/dist/vue-components14.es.js +61 -34
  31. package/dist/vue-components15.es.js +57 -24
  32. package/dist/vue-components16.es.js +20 -26
  33. package/dist/vue-components17.es.js +4 -6
  34. package/dist/vue-components18.es.js +78 -16
  35. package/dist/vue-components19.es.js +86 -30
  36. package/dist/vue-components20.es.js +72 -17
  37. package/dist/vue-components21.es.js +10 -19
  38. package/dist/vue-components22.es.js +54 -28
  39. package/dist/vue-components23.es.js +4 -6
  40. package/dist/vue-components24.es.js +43 -8
  41. package/dist/vue-components25.es.js +4 -37
  42. package/dist/vue-components26.es.js +83 -24
  43. package/dist/vue-components28.es.js +6 -22
  44. package/dist/vue-components29.es.js +8 -20
  45. package/dist/vue-components3.es.js +2 -2
  46. package/dist/vue-components30.es.js +267 -7
  47. package/dist/vue-components32.es.js +7 -4
  48. package/dist/vue-components33.es.js +71 -27
  49. package/dist/vue-components34.es.js +4 -4
  50. package/dist/vue-components35.es.js +50 -27
  51. package/dist/vue-components36.es.js +4 -5
  52. package/dist/vue-components37.es.js +23 -17
  53. package/dist/vue-components38.es.js +4 -55
  54. package/dist/vue-components39.es.js +57 -3
  55. package/dist/vue-components40.es.js +4 -43
  56. package/dist/vue-components41.es.js +11 -4
  57. package/dist/vue-components42.es.js +17 -79
  58. package/dist/vue-components44.es.js +8 -7
  59. package/dist/vue-components45.es.js +3 -8
  60. package/dist/vue-components46.es.js +36 -267
  61. package/dist/vue-components47.es.js +27 -0
  62. package/dist/vue-components48.es.js +27 -7
  63. package/dist/vue-components49.es.js +6 -79
  64. package/dist/vue-components50.es.js +17 -4
  65. package/dist/vue-components51.es.js +32 -69
  66. package/dist/vue-components52.es.js +17 -4
  67. package/dist/vue-components53.es.js +19 -22
  68. package/dist/vue-components54.es.js +29 -4
  69. package/dist/vue-components55.es.js +6 -58
  70. package/dist/vue-components56.es.js +8 -4
  71. package/dist/vue-components57.es.js +37 -11
  72. package/dist/vue-components58.es.js +24 -21
  73. package/dist/{vue-components27.es.js → vue-components59.es.js} +2 -2
  74. package/dist/vue-components6.es.js +11 -11
  75. package/dist/vue-components60.es.js +23 -8
  76. package/dist/vue-components61.es.js +18 -232
  77. package/dist/vue-components62.es.js +7 -31
  78. package/dist/vue-components63.es.js +19 -8
  79. package/dist/vue-components64.es.js +4 -35
  80. package/dist/vue-components65.es.js +29 -0
  81. package/dist/vue-components66.es.js +5 -0
  82. package/dist/vue-components67.es.js +29 -0
  83. package/dist/vue-components68.es.js +6 -0
  84. package/dist/vue-components69.es.js +18 -0
  85. package/dist/vue-components7.es.js +11 -26
  86. package/dist/vue-components70.es.js +40 -0
  87. package/dist/vue-components71.es.js +81 -0
  88. package/dist/vue-components72.es.js +33 -0
  89. package/dist/vue-components73.es.js +19 -0
  90. package/dist/vue-components74.es.js +48 -0
  91. package/dist/vue-components8.es.js +33 -45
  92. package/dist/vue-components9.es.js +46 -4
  93. package/package.json +7 -7
  94. package/src/components/CommandButton.vue +3 -1
  95. package/src/components/OmegaForm/OmegaArray.vue +1 -1
  96. package/src/components/OmegaForm/OmegaAutoGen.vue +2 -1
  97. package/src/components/OmegaForm/OmegaErrorsInternal.vue +1 -1
  98. package/src/components/OmegaForm/OmegaFormInput.vue +1 -1
  99. package/src/components/OmegaForm/OmegaInput.vue +6 -68
  100. package/src/components/OmegaForm/OmegaInputVuetify.vue +1 -1
  101. package/src/components/OmegaForm/OmegaInternalInput.vue +5 -11
  102. package/src/components/OmegaForm/OmegaTaggedUnion.vue +2 -1
  103. package/src/components/OmegaForm/OmegaWrapper.vue +1 -1
  104. package/src/components/OmegaForm/blockDialog.ts +10 -1
  105. package/src/components/OmegaForm/createUseFormWithCustomInput.ts +2 -1
  106. package/src/components/OmegaForm/errors.ts +136 -0
  107. package/src/components/OmegaForm/getOmegaStore.ts +1 -1
  108. package/src/components/OmegaForm/hocs.ts +19 -0
  109. package/src/components/OmegaForm/index.ts +16 -4
  110. package/src/components/OmegaForm/inputs.ts +22 -0
  111. package/src/components/OmegaForm/meta/checks.ts +81 -0
  112. package/src/components/OmegaForm/meta/createMeta.ts +138 -0
  113. package/src/components/OmegaForm/meta/defaults.ts +132 -0
  114. package/src/components/OmegaForm/meta/redacted.ts +66 -0
  115. package/src/components/OmegaForm/meta/types.ts +78 -0
  116. package/src/components/OmegaForm/meta/walker.ts +247 -0
  117. package/src/components/OmegaForm/persistency.ts +247 -0
  118. package/src/components/OmegaForm/submit.ts +128 -0
  119. package/src/components/OmegaForm/types.ts +751 -0
  120. package/src/components/OmegaForm/useOmegaForm.ts +49 -913
  121. package/src/components/OmegaForm/validation/localized.ts +202 -0
  122. package/dist/types/components/OmegaForm/OmegaFormStuff.d.ts +0 -173
  123. package/dist/vue-components31.es.js +0 -19
  124. package/src/components/OmegaForm/OmegaFormStuff.ts +0 -1422
@@ -1,12 +1,8 @@
1
1
  <template>
2
2
  <component
3
3
  :is="form.Field"
4
- :key="fieldKey"
5
4
  :name="name"
6
- :validators="{
7
- ...validators,
8
- onSubmit: schema
9
- }"
5
+ :validators="validators"
10
6
  >
11
7
  <template #default="{ field, state }">
12
8
  <OmegaInternalInput
@@ -44,12 +40,13 @@
44
40
  Name extends DeepKeys<From>
45
41
  "
46
42
  >
43
+ /* eslint-disable @typescript-eslint/no-explicit-any -- TanStack Form Field generic interop and slot prop typing */
47
44
  import { type DeepKeys } from "@tanstack/vue-form"
48
45
  import { computed, inject, type Ref, useAttrs } from "vue"
49
- import { useIntl } from "../../utils"
50
- import { type FieldMeta, generateInputStandardSchemaFromFieldMeta, type OmegaInputPropsBase } from "./OmegaFormStuff"
46
+ import { useErrorLabel } from "./errors"
47
+ import { type FieldMeta } from "./meta/types"
51
48
  import OmegaInternalInput from "./OmegaInternalInput.vue"
52
- import { useErrorLabel } from "./useOmegaForm"
49
+ import { type OmegaInputPropsBase } from "./types"
53
50
 
54
51
  const props = defineProps<OmegaInputPropsBase<From, To, Name>>()
55
52
 
@@ -61,13 +58,10 @@ defineSlots<{
61
58
  default?: (props: any) => any
62
59
  }>()
63
60
 
64
- defineOptions({
65
- inheritAttrs: false
66
- })
61
+ defineOptions({ inheritAttrs: false })
67
62
 
68
63
  const attrs = useAttrs()
69
64
 
70
- // Compute the class to use based on inputClass prop
71
65
  const computedClass = computed(() => {
72
66
  if (props.inputClass === null) return undefined
73
67
  if (props.inputClass !== undefined) return props.inputClass
@@ -86,61 +80,5 @@ const meta = computed(() => {
86
80
  return props.form.meta[propsName.value]
87
81
  })
88
82
 
89
- // Key to force Field re-mount when meta type changes (for TaggedUnion support)
90
- const fieldKey = computed(() => {
91
- const m = meta.value
92
- if (!m) return propsName.value
93
- // Include type and key constraints in the key so Field re-mounts when validation rules change
94
- // Cast to any since not all FieldMeta variants have these properties
95
- const fm = m as any
96
- return `${propsName.value}-${fm.type}-${fm.minLength ?? ""}-${fm.maxLength ?? ""}-${fm.minimum ?? ""}-${
97
- fm.maximum ?? ""
98
- }`
99
- })
100
-
101
- // Call useIntl during setup to avoid issues when computed re-evaluates
102
- const { trans } = useIntl()
103
-
104
- const hasIssues = (result: any): boolean => Array.isArray(result?.issues) && result.issues.length > 0
105
-
106
- const composeStandardSchemas = (
107
- omegaSchema: any,
108
- originalSchema: any
109
- ) => ({
110
- "~standard": {
111
- ...omegaSchema["~standard"],
112
- validate: (value: unknown) => {
113
- const omegaResult = omegaSchema["~standard"].validate(value)
114
- if (omegaResult && typeof omegaResult.then === "function") {
115
- return omegaResult.then((resolved: any) => {
116
- if (hasIssues(resolved)) {
117
- return resolved
118
- }
119
- return originalSchema["~standard"].validate(value)
120
- })
121
- }
122
-
123
- if (hasIssues(omegaResult)) {
124
- return omegaResult
125
- }
126
-
127
- return originalSchema["~standard"].validate(value)
128
- }
129
- }
130
- })
131
-
132
- const schema = computed(() => {
133
- if (!meta.value) {
134
- console.log(props.name, Object.keys(props.form.meta), props.form.meta)
135
- throw new Error("Meta is undefined")
136
- }
137
- const omegaSchema = generateInputStandardSchemaFromFieldMeta(meta.value, trans)
138
- const fieldSchema = meta.value.originalSchema
139
- if (fieldSchema) {
140
- return composeStandardSchemas(omegaSchema, fieldSchema)
141
- }
142
- return omegaSchema
143
- })
144
-
145
83
  const errori18n = useErrorLabel(props.form)
146
84
  </script>
@@ -207,8 +207,8 @@
207
207
  generic="From extends Record<PropertyKey, any>, Name extends DeepKeys<From>"
208
208
  >
209
209
  import { type DeepKeys } from "@tanstack/vue-form"
210
- import { getInputType } from "../OmegaForm/OmegaFormStuff"
211
210
  import type { VuetifyInputProps } from "./InputProps"
211
+ import { getInputType } from "./inputs"
212
212
 
213
213
  defineProps<VuetifyInputProps<From, Name>>()
214
214
 
@@ -24,11 +24,13 @@
24
24
  lang="ts"
25
25
  generic="From extends Record<PropertyKey, any>, Name extends DeepKeys<From>"
26
26
  >
27
+ /* eslint-disable @typescript-eslint/no-explicit-any -- TanStack Form / Vue attrs interop */
27
28
  import { type DeepKeys, useStore } from "@tanstack/vue-form"
28
29
  import { computed, type ComputedRef, getCurrentInstance, useAttrs, useId, useSlots } from "vue"
29
30
  import type { InputProps, OmegaFieldInternalApi } from "./InputProps"
30
- import type { FieldValidators, MetaRecord, NestedKeyOf, TypeOverride } from "./OmegaFormStuff"
31
+ import type { MetaRecord, NestedKeyOf } from "./meta/types"
31
32
  import OmegaInputVuetify from "./OmegaInputVuetify.vue"
33
+ import type { FieldValidators, TypeOverride } from "./types"
32
34
 
33
35
  defineOptions({
34
36
  inheritAttrs: false
@@ -84,7 +86,8 @@ const id = useId()
84
86
 
85
87
  const fieldApi = props.field
86
88
 
87
- const fieldState = useStore(fieldApi.store, (state) => state)
89
+ // Subscribed for side-effect: keeps component reactive to fieldApi.store changes
90
+ const _fieldState = useStore(fieldApi.store, (state) => state)
88
91
 
89
92
  // Get errors from form-level fieldMeta (persists across Field re-mounts)
90
93
  const formFieldMeta = useStore(fieldApi.form.store, (state) => state.fieldMeta)
@@ -119,7 +122,6 @@ const isFalsyButNotZero = (value: unknown): boolean => {
119
122
  // we remove value and errors when the field is empty and not required
120
123
  // convert nullish value to null or undefined based on schema
121
124
  const handleChange: OmegaFieldInternalApi<From, Name>["handleChange"] = (value) => {
122
- let fieldDeleted = false
123
125
  if (isFalsyButNotZero(value) && props.meta?.type !== "boolean") {
124
126
  // Only convert to null/undefined if the field is actually nullable or optional
125
127
  if (props.meta?.nullableOrUndefined) {
@@ -136,7 +138,6 @@ const handleChange: OmegaFieldInternalApi<From, Name>["handleChange"] = (value)
136
138
  // from `required: false`, which may also just mean "empty string
137
139
  // is valid" for unconstrained `S.String` fields.
138
140
  props.field.form.deleteField(props.field.name)
139
- fieldDeleted = true
140
141
  } else {
141
142
  // Keep the actual value (e.g., empty string for S.String fields)
142
143
  props.field.handleChange(value)
@@ -144,13 +145,6 @@ const handleChange: OmegaFieldInternalApi<From, Name>["handleChange"] = (value)
144
145
  } else {
145
146
  props.field.handleChange(value)
146
147
  }
147
-
148
- // whenever we change the field, regardless if we set it to null, we should reset onSubmit.
149
- // not sure why this is not the case in tanstack form.
150
- // Skip when the field was deleted — its meta no longer exists in the form store.
151
- if (!fieldDeleted) {
152
- props.field.setMeta((m) => ({ ...m, errorMap: { ...m.errorMap, onSubmit: undefined } }))
153
- }
154
148
  }
155
149
 
156
150
  // Note: Default value normalization (converting empty strings to null/undefined for nullable fields)
@@ -10,8 +10,8 @@
10
10
  import { type DeepKeys } from "@tanstack/vue-form"
11
11
  import { computed, provide, ref, watch } from "vue"
12
12
  import { type TaggedUnionOption } from "./InputProps"
13
- import { type FieldPath } from "./OmegaFormStuff"
14
13
  import OmegaTaggedUnionInternal from "./OmegaTaggedUnionInternal.vue"
14
+ import { type FieldPath } from "./types"
15
15
  import { type useOmegaForm } from "./useOmegaForm"
16
16
 
17
17
  const props = defineProps<{
@@ -32,6 +32,7 @@ watch(
32
32
  () => {
33
33
  const path = tagPath.value
34
34
  // Navigate to the nested value
35
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- traversing arbitrary nested values
35
36
  return path.split(".").reduce((acc: any, key) => acc?.[key], formValues.value) as string | null
36
37
  },
37
38
  (newTag) => {
@@ -32,7 +32,7 @@
32
32
  import { useStore } from "@tanstack/vue-form"
33
33
  import { usePreventClose } from "./blockDialog"
34
34
  import { getOmegaStore } from "./getOmegaStore"
35
- import { type DefaultTypeProps, type OmegaFormApi, type OmegaFormState } from "./OmegaFormStuff"
35
+ import { type DefaultTypeProps, type OmegaFormApi, type OmegaFormState } from "./types"
36
36
  import { type OmegaFormReturn } from "./useOmegaForm"
37
37
 
38
38
  type OmegaWrapperProps = {
@@ -1,5 +1,6 @@
1
1
  import mitt from "mitt"
2
2
  import { inject, type InjectionKey, provide, type Ref } from "vue"
3
+ import { useIntl } from "../../utils"
3
4
  import { onMountedWithCleanup } from "./onMountedWithCleanup"
4
5
 
5
6
  export type DialogClosing = { prevent?: boolean | Promise<boolean> }
@@ -19,11 +20,19 @@ export const usePreventClose = (mkIsDirty: () => Ref<boolean>) => {
19
20
  if (!bus) {
20
21
  return
21
22
  }
23
+ const { formatMessage, trans } = useIntl()
22
24
  const isDirty = mkIsDirty()
25
+ const defaultMessage = "There are unsaved changes. Are you sure you want to close?"
23
26
  onMountedWithCleanup(() => {
24
27
  const onDialogClosing = (evt: DialogClosing) => {
25
28
  if (isDirty.value) {
26
- if (!confirm("Es sind ungespeicherte Änderungen vorhanden. Wirklich schließen?")) {
29
+ // Mirror the guard pattern in errors.ts: a custom `useIntl` mock may
30
+ // only provide `trans`, so fall back through trans → defaultMessage.
31
+ const message = formatMessage
32
+ ? formatMessage({ id: "form.unsaved_changes_confirm", defaultMessage })
33
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- key may not be registered in the locale catalog
34
+ : trans?.("form.unsaved_changes_confirm" as any) ?? defaultMessage
35
+ if (!confirm(message)) {
27
36
  evt.prevent = true
28
37
  }
29
38
  }
@@ -1,8 +1,9 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any -- TanStack Form / Vue render-fn slot interop */
1
2
  import type { DeepKeys } from "@tanstack/vue-form"
2
3
  import { type Component, h } from "vue"
3
4
  import type { MergedInputProps } from "./InputProps"
4
- import { type DefaultTypeProps } from "./OmegaFormStuff"
5
5
  import OmegaInput from "./OmegaInput.vue"
6
+ import { type DefaultTypeProps } from "./types"
6
7
  import { useOmegaForm } from "./useOmegaForm"
7
8
 
8
9
  export const createUseFormWithCustomInput = <
@@ -0,0 +1,136 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+
3
+ import { type Component, computed, type ComputedRef, type ConcreteComponent, h, onUnmounted, type Ref, ref, watch } from "vue"
4
+ import { useIntl } from "../../utils"
5
+ import type { OmegaError } from "./types"
6
+ import type { OF } from "./useOmegaForm"
7
+
8
+ export const useErrorLabel = (form: OF<any, any>) => {
9
+ const { formatMessage } = useIntl()
10
+ const humanize = (str: string) => {
11
+ return str
12
+ .replace(/([A-Z])/g, " $1") // Add space before capital letters
13
+ .replace(/^./, (char) => char.toUpperCase()) // Capitalize the first letter
14
+ .trim() // Remove leading/trailing spaces
15
+ }
16
+ const fallback = (propsName: string) =>
17
+ formatMessage
18
+ ? formatMessage({ id: `general.fields.${propsName}`, defaultMessage: humanize(propsName) })
19
+ : humanize(propsName)
20
+ const i18n = (propsName: string) =>
21
+ form.i18nNamespace
22
+ ? formatMessage({ id: `${form.i18nNamespace}.fields.${propsName}`, defaultMessage: fallback(propsName) })
23
+ : fallback(propsName)
24
+
25
+ return i18n
26
+ }
27
+
28
+ export const eHoc = (errorProps: {
29
+ form: OF<any, any>
30
+ fieldMap: Ref<Map<string, { id: string; label: string }>>
31
+ }) => {
32
+ return function FormHoc<P>(
33
+ WrappedComponent: Component<P>
34
+ ): ConcreteComponent<P> {
35
+ return {
36
+ setup() {
37
+ const { fieldMap, form } = errorProps
38
+ const generalErrors = form.useStore((state) => state.errors)
39
+ const fieldMeta = form.useStore((state) => state.fieldMeta)
40
+ const errorMap = form.useStore((state) => state.errorMap)
41
+
42
+ const errorLabel = useErrorLabel(form)
43
+
44
+ const errors = computed(() => {
45
+ // Collect errors from fieldMeta (field-level errors for registered fields)
46
+ const fieldErrors = Object.entries(fieldMeta.value).reduce<OmegaError[]>((acc, [key, m]) => {
47
+ const fieldErrors = (m as { errors?: Array<{ message?: string }> } | undefined)?.errors ?? []
48
+ if (!fieldErrors.length) {
49
+ return acc
50
+ }
51
+
52
+ const fieldInfo = fieldMap.value.get(key)
53
+ if (!fieldInfo) {
54
+ return acc
55
+ }
56
+
57
+ acc.push({
58
+ label: fieldInfo.label,
59
+ inputId: fieldInfo.id,
60
+ errors: [fieldErrors[0]?.message].filter(Boolean) as string[]
61
+ })
62
+
63
+ return acc
64
+ }, [])
65
+
66
+ // Collect errors from errorMap.onDynamic / errorMap.onSubmit ONLY for fields that are NOT registered
67
+ // (registered fields already have their errors in fieldMeta).
68
+ // Our localized standard schema writes to onDynamic via validationLogic: revalidateLogic();
69
+ // caller-provided validators.onSubmit (via tanstackFormOptions spread) writes to onSubmit.
70
+ const submitErrors: OmegaError[] = []
71
+ const submitIssueMaps = [errorMap.value.onDynamic, errorMap.value.onSubmit].filter(
72
+ Boolean
73
+ ) as unknown as Array<
74
+ Record<string, unknown>
75
+ >
76
+ const seenPaths = new Set<string>()
77
+ for (const issuesByPath of submitIssueMaps) {
78
+ for (const [_, issues] of Object.entries(issuesByPath)) {
79
+ if (Array.isArray(issues) && issues.length) {
80
+ for (const issue of issues) {
81
+ const issAny: any = issue
82
+ if (issAny?.path && Array.isArray(issAny.path) && issAny.path.length) {
83
+ const fieldPath = issAny.path.join(".")
84
+ if (!fieldMap.value.has(fieldPath) && !seenPaths.has(fieldPath)) {
85
+ seenPaths.add(fieldPath)
86
+ submitErrors.push({
87
+ label: errorLabel(fieldPath),
88
+ inputId: fieldPath,
89
+ errors: [issAny.message].filter(Boolean)
90
+ })
91
+ break
92
+ }
93
+ }
94
+ }
95
+ }
96
+ }
97
+ }
98
+
99
+ // Combine both error sources (no need to check for duplicates since they're mutually exclusive)
100
+ return [...fieldErrors, ...submitErrors]
101
+ })
102
+
103
+ return {
104
+ generalErrors,
105
+ errors
106
+ }
107
+ },
108
+ render({ errors, generalErrors }: any) {
109
+ return h(WrappedComponent, {
110
+ errors,
111
+ generalErrors,
112
+ ...this.$attrs
113
+ } as any, this.$slots)
114
+ }
115
+ }
116
+ }
117
+ }
118
+
119
+ export const makeFieldMap = () => {
120
+ const fieldMap = ref(new Map<string, { label: string; id: string }>())
121
+ const registerField = (field: ComputedRef<{ name: string; label: string; id: string }>) => {
122
+ watch(field, (f) => {
123
+ fieldMap.value.set(f.name, { label: f.label, id: f.id })
124
+ }, { immediate: true })
125
+ onUnmounted(() => {
126
+ // Only delete if we still own this entry (id matches)
127
+ // This prevents old components from deleting entries registered by new components
128
+ // during re-mount transitions (e.g., when :key changes)
129
+ const currentEntry = fieldMap.value.get(field.value.name)
130
+ if (currentEntry?.id === field.value.id) {
131
+ fieldMap.value.delete(field.value.name)
132
+ }
133
+ })
134
+ }
135
+ return { fieldMap, registerField }
136
+ }
@@ -1,6 +1,6 @@
1
1
  import { useStore } from "@tanstack/vue-form"
2
2
  import { computed, type Ref } from "vue"
3
- import type { OmegaFormApi, OmegaFormState } from "./OmegaFormStuff"
3
+ import type { OmegaFormApi, OmegaFormState } from "./types"
4
4
 
5
5
  export function getOmegaStore<
6
6
  To,
@@ -0,0 +1,19 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+
3
+ import { type Component, type ConcreteComponent, h } from "vue"
4
+ import type { OF } from "./useOmegaForm"
5
+
6
+ export const fHoc = (form: OF<any, any>) => {
7
+ return function FormHoc<P>(
8
+ WrappedComponent: Component<P>
9
+ ): ConcreteComponent<P> {
10
+ return {
11
+ render() {
12
+ return h(WrappedComponent, {
13
+ form,
14
+ ...this.$attrs
15
+ } as any, this.$slots)
16
+ }
17
+ }
18
+ }
19
+ }
@@ -1,5 +1,17 @@
1
- export * from "./OmegaFormStuff"
2
- export { type OmegaConfig, type OmegaFormReturn, useOmegaForm } from "./useOmegaForm"
1
+ import type { S } from "effect-app"
2
+
3
+ export { getInputType, type SupportedInputs } from "./inputs"
4
+ export { createMeta, generateMetaFromSchema, isNullableOrUndefined, metadataFromAst } from "./meta/createMeta"
5
+ export type { CreateMeta, FilterItems } from "./meta/createMeta"
6
+ export { defaultsValueFromSchema } from "./meta/defaults"
7
+ export { toFormSchema } from "./meta/redacted"
8
+ export type { BaseFieldMeta, BooleanFieldMeta, DateFieldMeta, FieldMeta, MetaRecord, MultipleFieldMeta, NestedKeyOf, NumberFieldMeta, SelectFieldMeta, StringFieldMeta, UnknownFieldMeta } from "./meta/types"
9
+ export { deepMerge } from "./persistency"
10
+ export type { BaseProps, DefaultTypeProps, FieldPath, FieldPath_, FieldValidators, FormComponent, FormProps, FormType, OmegaArrayProps, OmegaAutoGenMeta, OmegaError, OmegaFormApi, OmegaFormParams, OmegaFormState, OmegaInputProps, OmegaInputPropsBase, PrefixFromDepth, TypeOverride, TypesWithOptions } from "./types"
11
+ export { makeStandardSchemaV1Hooks, toLocalizedStandardSchemaV1 } from "./validation/localized"
12
+
13
+ export { FormErrors, OmegaFormKey, useErrorLabel, useOmegaForm } from "./useOmegaForm"
14
+ export type { defaultValuesPriorityUnion, OF, OmegaConfig, OmegaFormReturn, Policies } from "./useOmegaForm"
3
15
 
4
16
  export { type ExtractTagValue, type ExtractUnionBranch, type InputProps, type MergedInputProps, type TaggedUnionOption, type TaggedUnionOptionsArray, type TaggedUnionProps } from "./InputProps"
5
17
  export { default as OmegaInput } from "./OmegaInput.vue"
@@ -9,6 +21,6 @@ export { default as OmegaTaggedUnionInternal } from "./OmegaTaggedUnionInternal.
9
21
 
10
22
  export { useOnClose, usePreventClose } from "./blockDialog"
11
23
 
12
- export { getInputType } from "./OmegaFormStuff"
13
-
14
24
  export { createUseFormWithCustomInput } from "./createUseFormWithCustomInput"
25
+
26
+ export const duplicateSchema = <From, To>(schema: S.Codec<To, From, never>) => schema
@@ -0,0 +1,22 @@
1
+ const supportedInputs = [
2
+ "button",
3
+ "checkbox",
4
+ "color",
5
+ "date",
6
+ "email",
7
+ "number",
8
+ "password",
9
+ "radio",
10
+ "range",
11
+ "search",
12
+ "submit",
13
+ "tel",
14
+ "text",
15
+ "time",
16
+ "url"
17
+ ] as const
18
+
19
+ export type SupportedInputs = typeof supportedInputs[number]
20
+
21
+ export const getInputType = (input: string): SupportedInputs =>
22
+ (supportedInputs as readonly string[]).includes(input) ? input as SupportedInputs : "text"
@@ -0,0 +1,81 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import { type Record, S } from "effect-app"
3
+ import type { FieldMeta } from "./types"
4
+
5
+ export const getCheckMetas = (property: S.AST.AST): Array<Record<string, any>> => {
6
+ const checks = property.checks ?? []
7
+
8
+ return checks.flatMap((check) => {
9
+ if (check._tag === "FilterGroup") {
10
+ return check.checks.flatMap((inner) => {
11
+ const meta = inner.annotations?.meta
12
+ return meta && typeof meta === "object" ? [meta as Record<string, any>] : []
13
+ })
14
+ }
15
+
16
+ const meta = check.annotations?.meta
17
+ return meta && typeof meta === "object" ? [meta as Record<string, any>] : []
18
+ })
19
+ }
20
+
21
+ export const getFieldMetadataFromAst = (property: S.AST.AST) => {
22
+ const base: Partial<FieldMeta> & Record<string, unknown> = {
23
+ description: S.AST.resolveDescription(property)
24
+ }
25
+ const checks = getCheckMetas(property)
26
+
27
+ if (S.AST.isString(property)) {
28
+ base.type = "string"
29
+ for (const check of checks) {
30
+ switch (check._tag) {
31
+ case "isMinLength":
32
+ base.minLength = check.minLength
33
+ break
34
+ case "isMaxLength":
35
+ base.maxLength = check.maxLength
36
+ break
37
+ }
38
+ }
39
+
40
+ const format = property.annotations?.["format"]
41
+ if (format === "email") {
42
+ base.format = "email"
43
+ }
44
+ } else if (S.AST.isNumber(property)) {
45
+ base.type = "number"
46
+ for (const check of checks) {
47
+ switch (check._tag) {
48
+ case "isInt":
49
+ base.refinement = "int"
50
+ break
51
+ case "isGreaterThanOrEqualTo":
52
+ base.minimum = check.minimum
53
+ break
54
+ case "isLessThanOrEqualTo":
55
+ base.maximum = check.maximum
56
+ break
57
+ case "isBetween":
58
+ base.minimum = check.minimum
59
+ base.maximum = check.maximum
60
+ break
61
+ case "isGreaterThan":
62
+ base.exclusiveMinimum = check.exclusiveMinimum
63
+ break
64
+ case "isLessThan":
65
+ base.exclusiveMaximum = check.exclusiveMaximum
66
+ break
67
+ }
68
+ }
69
+ } else if (S.AST.isBoolean(property)) {
70
+ base.type = "boolean"
71
+ } else if (
72
+ S.AST.isDeclaration(property)
73
+ && (property.annotations as any)?.typeConstructor?._tag === "Date"
74
+ ) {
75
+ base.type = "date"
76
+ } else {
77
+ base.type = "unknown"
78
+ }
79
+
80
+ return base
81
+ }
@@ -0,0 +1,138 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import { type Effect, type Record, S } from "effect-app"
3
+ import { getTransformationFrom } from "../../../utils"
4
+ import type { FieldMeta, MetaRecord } from "./types"
5
+ import { classifyAndWalkUnion, leafMetaForAst, type ParentMeta, type WalkerContext, walkStruct } from "./walker"
6
+
7
+ export type FilterItems = {
8
+ items: readonly [string, ...string[]]
9
+ message:
10
+ | string
11
+ | Effect.Effect<string, never, never>
12
+ | { readonly message: string | Effect.Effect<string> }
13
+ }
14
+
15
+ export type CreateMeta =
16
+ & {
17
+ parent?: string
18
+ meta?: Record<string, any>
19
+ nullableOrUndefined?: false | "undefined" | "null"
20
+ }
21
+ & (
22
+ | {
23
+ propertySignatures: readonly S.AST.PropertySignature[]
24
+ property?: never
25
+ }
26
+ | {
27
+ propertySignatures?: never
28
+ property: S.AST.AST
29
+ }
30
+ )
31
+
32
+ export const unwrapDeclaration = (property: S.AST.AST): S.AST.AST => {
33
+ let current = getTransformationFrom(property)
34
+
35
+ while (S.AST.isDeclaration(current) && current.typeParameters.length > 0) {
36
+ current = getTransformationFrom(current.typeParameters[0]!)
37
+ }
38
+
39
+ return current
40
+ }
41
+
42
+ export const isNullableOrUndefined = (property: false | S.AST.AST | undefined) => {
43
+ if (!property || !S.AST.isUnion(property)) return false
44
+ if (property.types.find((_) => S.AST.isUndefined(_))) {
45
+ return "undefined"
46
+ }
47
+ if (property.types.find((_) => S.AST.isNull(_))) return "null"
48
+ return false
49
+ }
50
+
51
+ export const createMeta = <T = any>(
52
+ { meta = {}, parent = "", property, propertySignatures }: CreateMeta,
53
+ acc: Partial<MetaRecord<T>> = {}
54
+ ): MetaRecord<T> | FieldMeta => {
55
+ const ctx: WalkerContext<T> = { acc, unionMeta: {} }
56
+
57
+ if (propertySignatures) {
58
+ const parentMeta: ParentMeta = {
59
+ required: meta.required !== false,
60
+ nullableOrUndefined: meta.nullableOrUndefined ?? false
61
+ }
62
+ walkStruct(propertySignatures, parent, parentMeta, ctx)
63
+ return acc
64
+ }
65
+
66
+ if (property) {
67
+ const nullableOrUndefined = isNullableOrUndefined(property)
68
+ const unwrapped = unwrapDeclaration(property)
69
+ const required = !Object.hasOwnProperty.call(meta, "required")
70
+ ? !nullableOrUndefined
71
+ : (meta.required as boolean)
72
+
73
+ const parentMeta: ParentMeta = {
74
+ required,
75
+ nullableOrUndefined: (meta.nullableOrUndefined ?? nullableOrUndefined) as false | "null" | "undefined"
76
+ }
77
+
78
+ if (S.AST.isObjects(unwrapped)) {
79
+ walkStruct(unwrapped.propertySignatures, parent, parentMeta, ctx)
80
+ return acc
81
+ }
82
+
83
+ if (S.AST.isUnion(unwrapped)) {
84
+ // For property-mode, return a FieldMeta by running through classifyAndWalkUnion
85
+ // and then pulling out the result at `parent` key
86
+ const leafCtx: WalkerContext<T> = { acc: {}, unionMeta: {} }
87
+ classifyAndWalkUnion(unwrapped, parent, parentMeta, leafCtx)
88
+ const result = (leafCtx.acc as any)[parent]
89
+ if (result) return result as FieldMeta
90
+ }
91
+
92
+ return leafMetaForAst(unwrapped, parentMeta)
93
+ }
94
+
95
+ return acc
96
+ }
97
+
98
+ export const metadataFromAst = <From, To>(
99
+ schema: S.Codec<To, From, never>
100
+ ): {
101
+ meta: MetaRecord<To>
102
+ defaultValues: Record<string, any>
103
+ unionMeta: Record<string, MetaRecord<To>>
104
+ } => {
105
+ const ast = unwrapDeclaration(schema.ast)
106
+ const newMeta: Partial<MetaRecord<To>> = {}
107
+ const defaultValues: Record<string, any> = {}
108
+ const unionMeta: Record<string, MetaRecord<To>> = {}
109
+
110
+ const ctx: WalkerContext<To> = { acc: newMeta, unionMeta }
111
+
112
+ if (S.AST.isUnion(ast)) {
113
+ // Root-level discriminated union
114
+ classifyAndWalkUnion(ast, "", { required: true, nullableOrUndefined: false }, ctx)
115
+
116
+ return { meta: newMeta as MetaRecord<To>, defaultValues, unionMeta }
117
+ }
118
+
119
+ if (S.AST.isObjects(ast)) {
120
+ walkStruct(ast.propertySignatures, "", { required: true, nullableOrUndefined: false }, ctx)
121
+
122
+ return { meta: newMeta as MetaRecord<To>, defaultValues, unionMeta }
123
+ }
124
+
125
+ return { meta: newMeta as MetaRecord<To>, defaultValues, unionMeta }
126
+ }
127
+
128
+ export const generateMetaFromSchema = <From, To>(
129
+ schema: S.Codec<To, From, never>
130
+ ): {
131
+ schema: S.Codec<To, From, never>
132
+ meta: MetaRecord<To>
133
+ unionMeta: Record<string, MetaRecord<To>>
134
+ } => {
135
+ const { meta, unionMeta } = metadataFromAst(schema)
136
+
137
+ return { schema, meta, unionMeta }
138
+ }