diff --git a/.changeset/whole-dogs-bow.md b/.changeset/whole-dogs-bow.md new file mode 100644 index 0000000000..75d474c2dc --- /dev/null +++ b/.changeset/whole-dogs-bow.md @@ -0,0 +1,5 @@ +--- +"@effect-app/vue-components": minor +--- + +Improves type handling for Input Props Name, that now only accept leaves schema values and not every valid key of the schema diff --git a/packages/vue-components/src/components/OmegaForm/OmegaArray.vue b/packages/vue-components/src/components/OmegaForm/OmegaArray.vue index a7777246df..7aeede001d 100644 --- a/packages/vue-components/src/components/OmegaForm/OmegaArray.vue +++ b/packages/vue-components/src/components/OmegaForm/OmegaArray.vue @@ -48,20 +48,11 @@ To extends Record " > -import { type DeepKeys, type DeepValue } from "@tanstack/vue-form" +import { type DeepKeys } from "@tanstack/vue-form" import { computed, onMounted, provide } from "vue" -import { type OmegaInputProps } from "./OmegaFormStuff" +import { type OmegaArrayProps } from "./OmegaFormStuff" -const props = defineProps< - Omit< - OmegaInputProps, - "validators" | "options" | "label" | "type" | "items" - > & { - defaultItems?: DeepValue> - // deprecated items, caused bugs in state update, use defaultItems instead. It's not a simple Never, because Volar explodes - items?: "please use `defaultItems` instead" - } ->() +const props = defineProps>() defineOptions({ inheritAttrs: false diff --git a/packages/vue-components/src/components/OmegaForm/OmegaFormStuff.ts b/packages/vue-components/src/components/OmegaForm/OmegaFormStuff.ts index ca279d02fb..8831cbbbac 100644 --- a/packages/vue-components/src/components/OmegaForm/OmegaFormStuff.ts +++ b/packages/vue-components/src/components/OmegaForm/OmegaFormStuff.ts @@ -1,17 +1,29 @@ import { type Effect, Option, type Record, S } from "effect-app" /* eslint-disable @typescript-eslint/no-explicit-any */ import { getMetadataFromSchema } from "@effect-app/vue/form" -import { type DeepKeys, type FieldAsyncValidateOrFn, type FieldValidateOrFn, type FormApi, type FormAsyncValidateOrFn, type FormOptions, type FormState, type FormValidateOrFn, type StandardSchemaV1, type VueFormApi } from "@tanstack/vue-form" +import { type DeepKeys, type DeepValue, type FieldAsyncValidateOrFn, type FieldValidateOrFn, type FormApi, type FormAsyncValidateOrFn, type FormOptions, type FormState, type FormValidateOrFn, type StandardSchemaV1, type VueFormApi } from "@tanstack/vue-form" import { type RuntimeFiber } from "effect/Fiber" import { getTransformationFrom, useIntl } from "../../utils" import { type OmegaFieldInternalApi } from "./InputProps" import { type OF, type OmegaFormReturn } from "./useOmegaForm" -export type BaseProps> = { +type Leaves = T extends ReadonlyArray ? Leaves & {} + : { + [K in keyof T]: T[K] extends string | boolean | number | null | undefined | symbol | bigint + ? `${Path extends "" ? "" : `${Path}.`}${K & string}` + : Leaves & {} + }[keyof T] + +// Helper type to make array indices flexible - accepts both [number] and numeric literals [0], [1], etc. +type FlexibleArrayPath = T extends `${infer Before}[number]${infer After}` + ? T | `${Before}[${number}]${FlexibleArrayPath}` + : T + +export type BaseProps = DeepKeys> = { /** Will fallback to i18n when not specified */ label?: string validators?: FieldValidators - name: TName + name: TName & FlexibleArrayPath> } export type TypesWithOptions = "radio" | "select" | "multiple" | "autocomplete" | "autocompletemultiple" @@ -49,6 +61,21 @@ export type OmegaInputProps< } } & BaseProps> +export type OmegaArrayProps< + From extends Record, + To extends Record +> = + & Omit< + OmegaInputProps, + "validators" | "options" | "label" | "type" | "items" | "name" + > + & { + name: DeepKeys + defaultItems?: DeepValue> + // deprecated items, caused bugs in state update, use defaultItems instead. It's not a simple Never, because Volar explodes + items?: "please use `defaultItems` instead" + } + export type TypeOverride = | "string" | "text" diff --git a/packages/vue-components/src/components/OmegaForm/OmegaInput.vue b/packages/vue-components/src/components/OmegaForm/OmegaInput.vue index ceae22f7d9..870ad32dac 100644 --- a/packages/vue-components/src/components/OmegaForm/OmegaInput.vue +++ b/packages/vue-components/src/components/OmegaForm/OmegaInput.vue @@ -34,6 +34,7 @@ To extends Record " > +import { type DeepKeys } from "@tanstack/vue-form" import { computed, inject, type Ref } from "vue" import { useIntl } from "../../utils" import { type FieldMeta, generateInputStandardSchemaFromFieldMeta, type OmegaInputPropsBase } from "./OmegaFormStuff" @@ -41,6 +42,9 @@ import OmegaInternalInput from "./OmegaInternalInput.vue" const props = defineProps>() +// downgrade to DeepKeys to avoid useless and possible infinite recursion in TS +const propsName: Ref> = computed(() => props.name) + defineOptions({ inheritAttrs: false }) @@ -52,9 +56,9 @@ const getMetaFromArray = inject FieldMeta | null> | null>( const meta = computed(() => { if (getMetaFromArray?.value && getMetaFromArray.value(props.name)) { - return getMetaFromArray.value(props.name) + return getMetaFromArray.value(propsName.value) } - return props.form.meta[props.name] + return props.form.meta[propsName.value] }) const schema = computed(() => { @@ -72,9 +76,9 @@ const humanize = (str: string) => { .replace(/^./, (char) => char.toUpperCase()) // Capitalize the first letter .trim() // Remove leading/trailing spaces } -const fallback = () => formatMessage({ id: `general.fields.${props.name}`, defaultMessage: humanize(props.name) }) +const fallback = () => formatMessage({ id: `general.fields.${propsName.value}`, defaultMessage: humanize(props.name) }) const i18n = () => props.form.i18nNamespace - ? formatMessage({ id: `${props.form.i18nNamespace}.fields.${props.name}`, defaultMessage: fallback() }) + ? formatMessage({ id: `${props.form.i18nNamespace}.fields.${propsName.value}`, defaultMessage: fallback() }) : fallback() diff --git a/packages/vue-components/src/components/OmegaForm/useOmegaForm.ts b/packages/vue-components/src/components/OmegaForm/useOmegaForm.ts index e29468877b..f47fba1d10 100644 --- a/packages/vue-components/src/components/OmegaForm/useOmegaForm.ts +++ b/packages/vue-components/src/components/OmegaForm/useOmegaForm.ts @@ -12,7 +12,7 @@ import { MergedInputProps } from "./InputProps" import OmegaArray from "./OmegaArray.vue" import OmegaAutoGen from "./OmegaAutoGen.vue" import OmegaErrorsInternal from "./OmegaErrorsInternal.vue" -import { BaseProps, DefaultTypeProps, type FormProps, generateMetaFromSchema, type MetaRecord, type NestedKeyOf, OmegaAutoGenMeta, OmegaError, type OmegaFormApi, OmegaFormState, OmegaInputProps } from "./OmegaFormStuff" +import { BaseProps, DefaultTypeProps, type FormProps, generateMetaFromSchema, type MetaRecord, type NestedKeyOf, OmegaArrayProps, OmegaAutoGenMeta, OmegaError, type OmegaFormApi, OmegaFormState } from "./OmegaFormStuff" import OmegaInput from "./OmegaInput.vue" import OmegaForm from "./OmegaWrapper.vue" @@ -234,10 +234,7 @@ export interface OmegaFormReturn< >, never > - & (Omit, "validators" | "options" | "label" | "type" | "items" | "form"> & { - defaultItems?: DeepValue> - items?: "please use `defaultItems` instead" - }) + & (OmegaArrayProps) & {} > & import("vue").PublicProps diff --git a/packages/vue-components/stories/OmegaForm/SimpleForm.vue b/packages/vue-components/stories/OmegaForm/SimpleForm.vue index 5c51256b82..3d78540075 100644 --- a/packages/vue-components/stories/OmegaForm/SimpleForm.vue +++ b/packages/vue-components/stories/OmegaForm/SimpleForm.vue @@ -4,7 +4,7 @@
values: {{ values }} {{ isDirty }}