@effect-app/vue-components 2.11.5 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,7 @@
1
1
  <template>
2
2
  <component
3
3
  :is="form.Field"
4
+ :key="fieldKey"
4
5
  :name="name"
5
6
  :validators="{
6
7
  onChange: schema,
@@ -75,12 +76,22 @@ const getMetaFromArray = inject<Ref<(name: string) => FieldMeta | null> | null>(
75
76
  )
76
77
 
77
78
  const meta = computed(() => {
78
- const fromArray = getMetaFromArray?.value?.(props.name as DeepKeys<From>)
79
- if (fromArray) {
80
- return fromArray
79
+ if (getMetaFromArray?.value && getMetaFromArray.value(props.name as DeepKeys<From>)) {
80
+ return getMetaFromArray.value(propsName.value)
81
81
  }
82
- const formMeta = props.form.meta[propsName.value]
83
- return formMeta
82
+ return props.form.meta[propsName.value]
83
+ })
84
+
85
+ // Key to force Field re-mount when meta type changes (for TaggedUnion support)
86
+ const fieldKey = computed(() => {
87
+ const m = meta.value
88
+ if (!m) return propsName.value
89
+ // Include type and key constraints in the key so Field re-mounts when validation rules change
90
+ // Cast to any since not all FieldMeta variants have these properties
91
+ const fm = m as any
92
+ return `${propsName.value}-${fm.type}-${fm.minLength ?? ""}-${fm.maxLength ?? ""}-${fm.minimum ?? ""}-${
93
+ fm.maximum ?? ""
94
+ }`
84
95
  })
85
96
 
86
97
  // Call useIntl during setup to avoid issues when computed re-evaluates
@@ -84,6 +84,9 @@ const fieldApi = props.field
84
84
 
85
85
  const fieldState = useStore(fieldApi.store, (state) => state)
86
86
 
87
+ // Get errors from form-level fieldMeta (persists across Field re-mounts)
88
+ const formFieldMeta = useStore(fieldApi.form.store, (state) => state.fieldMeta)
89
+
87
90
  const fieldType = computed(() => {
88
91
  if (props.type) return props.type
89
92
  if (props.meta?.type === "string") {
@@ -95,8 +98,12 @@ const fieldType = computed(() => {
95
98
 
96
99
  props.register(computed(() => ({ name: props.field.name, label: props.label, id })))
97
100
 
98
- // workaround strange tanstack form issue where the errors key becomes undefined ???
99
- const _errors = computed(() => fieldState.value.meta.errors ?? [])
101
+ // Get errors from form-level fieldMeta instead of field-level state
102
+ // This ensures errors persist when Field components re-mount due to :key changes
103
+ const _errors = computed(() => {
104
+ const fieldMeta = formFieldMeta.value[props.field.name] as any
105
+ return fieldMeta?.errors ?? []
106
+ })
100
107
  const errors = computed(() =>
101
108
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
102
109
  _errors.value.map((e: any) => e?.message).filter(Boolean)
@@ -34,7 +34,6 @@ watch(
34
34
  },
35
35
  (newTag) => {
36
36
  currentTag.value = newTag ?? null
37
- return undefined
38
37
  },
39
38
  { immediate: true }
40
39
  )
@@ -12,7 +12,7 @@
12
12
  generic="From extends Record<PropertyKey, any>, To extends Record<PropertyKey, any>, Name extends DeepKeys<From>"
13
13
  >
14
14
  import { type DeepKeys, type DeepValue } from "@tanstack/vue-form"
15
- import { nextTick, watch } from "vue"
15
+ import { watch } from "vue"
16
16
  import { type OmegaFieldInternalApi } from "./InputProps"
17
17
  import { type useOmegaForm } from "./useOmegaForm"
18
18
 
@@ -23,30 +23,19 @@ const props = defineProps<{
23
23
  form: ReturnType<typeof useOmegaForm<From, To>>
24
24
  }>()
25
25
 
26
+ const values = props.form.useStore(({ values }) => values)
27
+
26
28
  // Watch for _tag changes
27
29
  watch(() => props.state, (newTag, oldTag) => {
28
30
  if (newTag === null) {
29
31
  props.field.setValue(null as DeepValue<From, Name>)
30
32
  }
31
33
 
32
- if (newTag !== oldTag && newTag) {
33
- // Use nextTick to avoid cleanup conflicts during reactive updates
34
- nextTick(() => {
35
- // Get default values for the new tag to ensure correct types
36
- const tagDefaults = (props.form as any).unionDefaultValues?.[newTag as string] ?? {}
37
- // Get current form values to preserve user's selections (e.g., carrier)
38
- const currentValues = props.form.state.values
39
- // Merge: keep current values, override with tag defaults for type correctness, set new _tag
40
- const resetValues = {
41
- ...currentValues,
42
- ...tagDefaults,
43
- _tag: newTag
44
- }
45
- props.form.reset(resetValues as any)
46
- setTimeout(() => {
47
- props.field.validate("change")
48
- }, 0)
49
- })
34
+ if (newTag !== oldTag) {
35
+ props.form.reset(values.value)
36
+ setTimeout(() => {
37
+ props.field.validate("change")
38
+ }, 0)
50
39
  }
51
40
  }, { immediate: true })
52
41
  </script>
@@ -218,7 +218,6 @@ export type OmegaConfig<T> = {
218
218
  export interface OF<From, To> extends OmegaFormApi<From, To> {
219
219
  meta: MetaRecord<From>
220
220
  unionMeta: Record<string, MetaRecord<From>>
221
- unionDefaultValues: Record<string, Record<string, any>>
222
221
  clear: () => void
223
222
  i18nNamespace?: string
224
223
  ignorePreventCloseEvents?: boolean
@@ -684,7 +683,7 @@ export const useOmegaForm = <
684
683
  const standardSchema = S.standardSchemaV1(schema)
685
684
  const decode = S.decode(schema)
686
685
 
687
- const { meta, unionDefaultValues, unionMeta } = generateMetaFromSchema(schema)
686
+ const { meta, unionMeta } = generateMetaFromSchema(schema)
688
687
 
689
688
  const persistencyKey = computed(() => {
690
689
  if (omegaConfig?.persistency?.id) {
@@ -919,7 +918,6 @@ export const useOmegaForm = <
919
918
  // Reset with current values to mark them as the new baseline
920
919
  form.reset(values.value)
921
920
  }
922
- return undefined
923
921
  })
924
922
  }
925
923
 
@@ -956,7 +954,6 @@ export const useOmegaForm = <
956
954
  ignorePreventCloseEvents: omegaConfig?.ignorePreventCloseEvents,
957
955
  meta,
958
956
  unionMeta,
959
- unionDefaultValues,
960
957
  clear,
961
958
  handleSubmit: (meta?: Record<string, any>) => {
962
959
  const span = api.trace.getSpan(api.context.active())
@@ -965,18 +962,8 @@ export const useOmegaForm = <
965
962
  // /** @experimental */
966
963
  handleSubmitEffect,
967
964
  registerField: (field: ComputedRef<{ name: string; label: string; id: string }>) => {
968
- watch(field, (f) => {
969
- fieldMap.value.set(f.name, { label: f.label, id: f.id })
970
- }, { immediate: true })
971
- onUnmounted(() => {
972
- // Only delete if this component instance still owns the registration (id matches)
973
- // This prevents the old component from removing the new component's registration
974
- // when Vue re-keys and mounts new before unmounting old
975
- const current = fieldMap.value.get(field.value.name)
976
- if (current?.id === field.value.id) {
977
- fieldMap.value.delete(field.value.name)
978
- }
979
- })
965
+ watch(field, (f) => fieldMap.value.set(f.name, { label: f.label, id: f.id }), { immediate: true })
966
+ onUnmounted(() => fieldMap.value.delete(field.value.name)) // todo; perhap only when owned (id match)
980
967
  }
981
968
  })
982
969
 
@@ -5,9 +5,7 @@ const Key = Symbol("injected") as InjectionKey<Map<string, { label: string; id:
5
5
 
6
6
  export const useRegisterField = (field: ComputedRef<{ name: string; label: string; id: string }>) => {
7
7
  const map = injectCertain(Key)
8
- watch(field, (f) => {
9
- map.set(f.name, { label: f.label, id: f.id })
10
- }, { immediate: true })
8
+ watch(field, (f) => map.set(f.name, { label: f.label, id: f.id }), { immediate: true })
11
9
  onUnmounted(() => map.delete(field.value.name)) // todo; perhap only when owned
12
10
  }
13
11
 
@@ -1,4 +0,0 @@
1
- import f from "./vue-components.es18.js";
2
- export {
3
- f as default
4
- };
@@ -1,34 +0,0 @@
1
- import { defineComponent as m, createElementBlock as d, openBlock as u, withModifiers as f, createElementVNode as l, unref as s, renderSlot as a } from "vue";
2
- import { useStore as b } from "@tanstack/vue-form";
3
- import { usePreventClose as p } from "./vue-components.es11.js";
4
- import { getOmegaStore as c } from "./vue-components.es53.js";
5
- const S = ["disabled"], V = /* @__PURE__ */ m({
6
- __name: "OmegaWrapper",
7
- props: {
8
- form: {},
9
- disabled: { type: Boolean },
10
- subscribe: {}
11
- },
12
- setup(o) {
13
- const e = o, i = b(
14
- e.form.store,
15
- (t) => t.isSubmitting
16
- ), n = c(
17
- e.form,
18
- e.subscribe
19
- );
20
- return e.form.ignorePreventCloseEvents || p(() => e.form.useStore((t) => t.isDirty)), (t, r) => (u(), d("form", {
21
- novalidate: "",
22
- onSubmit: r[0] || (r[0] = f((v) => o.form.handleSubmit(), ["prevent", "stop"]))
23
- }, [
24
- l("fieldset", {
25
- disabled: s(i) || o.disabled
26
- }, [
27
- a(t.$slots, "default", { subscribedValues: s(n) }, void 0, !0)
28
- ], 8, S)
29
- ], 32));
30
- }
31
- });
32
- export {
33
- V as default
34
- };