Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/gold-clowns-move.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@effect-app/vue-components": major
---

restore omegaform unions without reset
31 changes: 7 additions & 24 deletions packages/vue-components/src/components/OmegaForm/OmegaFormStuff.ts
Original file line number Diff line number Diff line change
Expand Up @@ -356,7 +356,6 @@ export const createMeta = <T = any>(

if (property?._tag === "TypeLiteral" && "propertySignatures" in property) {
return createMeta<T>({
parent, // Pass parent to maintain the key prefix for nested structures
meta,
propertySignatures: property.propertySignatures
})
Expand Down Expand Up @@ -405,13 +404,7 @@ export const createMeta = <T = any>(
property: p.type,
meta: { required: isRequired, nullableOrUndefined }
})
// If parentMeta is a MetaRecord (nested structure from ExtendedClass), merge it
// Otherwise assign as single FieldMeta
if (parentMeta && typeof parentMeta === "object" && !("type" in parentMeta)) {
Object.assign(acc, parentMeta)
} else {
acc[key as NestedKeyOf<T>] = parentMeta as FieldMeta
}
acc[key as NestedKeyOf<T>] = parentMeta as FieldMeta
}

// Process each non-null type and merge their metadata
Expand Down Expand Up @@ -708,17 +701,11 @@ const flattenMeta = <T>(meta: MetaRecord<T> | FieldMeta, parentKey: string = "")

const metadataFromAst = <From, To>(
schema: S.Schema<To, From, never>
): {
meta: MetaRecord<To>
defaultValues: Record<string, any>
unionMeta: Record<string, MetaRecord<To>>
unionDefaultValues: Record<string, Record<string, any>>
} => {
): { meta: MetaRecord<To>; defaultValues: Record<string, any>; unionMeta: Record<string, MetaRecord<To>> } => {
const ast = schema.ast
const newMeta: MetaRecord<To> = {}
const defaultValues: Record<string, any> = {}
const unionMeta: Record<string, MetaRecord<To>> = {}
const unionDefaultValues: Record<string, Record<string, any>> = {}

if (ast._tag === "Transformation" || ast._tag === "Refinement") {
return metadataFromAst(S.make(ast.from))
Expand Down Expand Up @@ -763,9 +750,6 @@ const metadataFromAst = <From, To>(
// Store per-tag metadata for reactive lookup
if (tagValue) {
unionMeta[tagValue] = flattenMeta<To>(memberMeta)
// Create default values for this tag's schema
const memberSchema = S.make(memberType)
unionDefaultValues[tagValue] = defaultsValueFromSchema(memberSchema as any)
}

// Merge into result (for backward compatibility)
Expand All @@ -782,7 +766,7 @@ const metadataFromAst = <From, To>(
} as FieldMeta
}

return { meta: newMeta, defaultValues, unionMeta, unionDefaultValues }
return { meta: newMeta, defaultValues, unionMeta }
}
}

Expand All @@ -792,7 +776,7 @@ const metadataFromAst = <From, To>(
})

if (Object.values(meta).every((value) => value && "type" in value)) {
return { meta: meta as MetaRecord<To>, defaultValues, unionMeta, unionDefaultValues }
return { meta: meta as MetaRecord<To>, defaultValues, unionMeta }
}

const flattenObject = (
Expand All @@ -812,7 +796,7 @@ const metadataFromAst = <From, To>(
flattenObject(meta)
}

return { meta: newMeta, defaultValues, unionMeta, unionDefaultValues }
return { meta: newMeta, defaultValues, unionMeta }
}

export const duplicateSchema = <From, To>(
Expand All @@ -827,11 +811,10 @@ export const generateMetaFromSchema = <From, To>(
schema: S.Schema<To, From, never>
meta: MetaRecord<To>
unionMeta: Record<string, MetaRecord<To>>
unionDefaultValues: Record<string, Record<string, any>>
} => {
const { meta, unionDefaultValues, unionMeta } = metadataFromAst(schema)
const { meta, unionMeta } = metadataFromAst(schema)

return { schema, meta, unionMeta, unionDefaultValues }
return { schema, meta, unionMeta }
}

export const generateInputStandardSchemaFromFieldMeta = (
Expand Down
17 changes: 12 additions & 5 deletions packages/vue-components/src/components/OmegaForm/OmegaInput.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<template>
<component
:is="form.Field"
:key="fieldKey"
:name="name"
:validators="{
onChange: schema,
Expand Down Expand Up @@ -75,12 +76,18 @@ const getMetaFromArray = inject<Ref<(name: string) => FieldMeta | null> | null>(
)

const meta = computed(() => {
const fromArray = getMetaFromArray?.value?.(props.name as DeepKeys<From>)
if (fromArray) {
return fromArray
if (getMetaFromArray?.value && getMetaFromArray.value(props.name as DeepKeys<From>)) {
return getMetaFromArray.value(propsName.value)
}
const formMeta = props.form.meta[propsName.value]
return formMeta
return props.form.meta[propsName.value]
})

// Key to force Field re-mount when meta type changes (for TaggedUnion support)
const fieldKey = computed(() => {
const m = meta.value
if (!m) return propsName.value
// Include type and key constraints in the key so Field re-mounts when validation rules change
return `${propsName.value}-${m.type}-${m.minLength ?? ""}-${m.maxLength ?? ""}-${m.minimum ?? ""}-${m.maximum ?? ""}`
})

// Call useIntl during setup to avoid issues when computed re-evaluates
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ watch(
},
(newTag) => {
currentTag.value = newTag ?? null
return undefined
},
{ immediate: true }
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
generic="From extends Record<PropertyKey, any>, To extends Record<PropertyKey, any>, Name extends DeepKeys<From>"
>
import { type DeepKeys, type DeepValue } from "@tanstack/vue-form"
import { nextTick, watch } from "vue"
import { watch } from "vue"
import { type OmegaFieldInternalApi } from "./InputProps"
import { type useOmegaForm } from "./useOmegaForm"

Expand All @@ -23,30 +23,19 @@ const props = defineProps<{
form: ReturnType<typeof useOmegaForm<From, To>>
}>()

const values = props.form.useStore(({ values }) => values)

// Watch for _tag changes
watch(() => props.state, (newTag, oldTag) => {
if (newTag === null) {
props.field.setValue(null as DeepValue<From, Name>)
}

if (newTag !== oldTag && newTag) {
// Use nextTick to avoid cleanup conflicts during reactive updates
nextTick(() => {
// Get default values for the new tag to ensure correct types
const tagDefaults = (props.form as any).unionDefaultValues?.[newTag as string] ?? {}
// Get current form values to preserve user's selections (e.g., carrier)
const currentValues = props.form.state.values
// Merge: keep current values, override with tag defaults for type correctness, set new _tag
const resetValues = {
...currentValues,
...tagDefaults,
_tag: newTag
}
props.form.reset(resetValues as any)
setTimeout(() => {
props.field.validate("change")
}, 0)
})
if (newTag !== oldTag) {
props.form.reset(values.value)
setTimeout(() => {
props.field.validate("change")
}, 0)
}
}, { immediate: true })
</script>
19 changes: 3 additions & 16 deletions packages/vue-components/src/components/OmegaForm/useOmegaForm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,6 @@ export type OmegaConfig<T> = {
export interface OF<From, To> extends OmegaFormApi<From, To> {
meta: MetaRecord<From>
unionMeta: Record<string, MetaRecord<From>>
unionDefaultValues: Record<string, Record<string, any>>
clear: () => void
i18nNamespace?: string
ignorePreventCloseEvents?: boolean
Expand Down Expand Up @@ -684,7 +683,7 @@ export const useOmegaForm = <
const standardSchema = S.standardSchemaV1(schema)
const decode = S.decode(schema)

const { meta, unionDefaultValues, unionMeta } = generateMetaFromSchema(schema)
const { meta, unionMeta } = generateMetaFromSchema(schema)

const persistencyKey = computed(() => {
if (omegaConfig?.persistency?.id) {
Expand Down Expand Up @@ -919,7 +918,6 @@ export const useOmegaForm = <
// Reset with current values to mark them as the new baseline
form.reset(values.value)
}
return undefined
})
}

Expand Down Expand Up @@ -956,7 +954,6 @@ export const useOmegaForm = <
ignorePreventCloseEvents: omegaConfig?.ignorePreventCloseEvents,
meta,
unionMeta,
unionDefaultValues,
clear,
handleSubmit: (meta?: Record<string, any>) => {
const span = api.trace.getSpan(api.context.active())
Expand All @@ -965,18 +962,8 @@ export const useOmegaForm = <
// /** @experimental */
handleSubmitEffect,
registerField: (field: ComputedRef<{ name: string; label: string; id: string }>) => {
watch(field, (f) => {
fieldMap.value.set(f.name, { label: f.label, id: f.id })
}, { immediate: true })
onUnmounted(() => {
// Only delete if this component instance still owns the registration (id matches)
// This prevents the old component from removing the new component's registration
// when Vue re-keys and mounts new before unmounting old
const current = fieldMap.value.get(field.value.name)
if (current?.id === field.value.id) {
fieldMap.value.delete(field.value.name)
}
})
watch(field, (f) => fieldMap.value.set(f.name, { label: f.label, id: f.id }), { immediate: true })
onUnmounted(() => fieldMap.value.delete(field.value.name)) // todo; perhap only when owned (id match)
}
})

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@ const Key = Symbol("injected") as InjectionKey<Map<string, { label: string; id:

export const useRegisterField = (field: ComputedRef<{ name: string; label: string; id: string }>) => {
const map = injectCertain(Key)
watch(field, (f) => {
map.set(f.name, { label: f.label, id: f.id })
}, { immediate: true })
watch(field, (f) => map.set(f.name, { label: f.label, id: f.id }), { immediate: true })
onUnmounted(() => map.delete(field.value.name)) // todo; perhap only when owned
}

Expand Down
Loading