diff --git a/fields/core/select/edit-component.tsx b/fields/core/select/edit-component.tsx index c0f4c432a..2b5493a96 100644 --- a/fields/core/select/edit-component.tsx +++ b/fields/core/select/edit-component.tsx @@ -1,6 +1,7 @@ "use client"; -import { useMemo } from "react"; +import { useMemo, useState } from "react"; +import { PlusIcon } from "lucide-react"; import { cn } from "@/lib/utils"; import { Combobox, @@ -37,11 +38,14 @@ const EditComponent = (props: any) => { const { value, field, onChange } = props; const isReadonly = Boolean(field?.readonly); const multiple = Boolean(field.options?.multiple); + const creatable = Boolean(field.options?.creatable); const storeAsObject = field?.type === "reference" && field.options?.store === "object"; const anchor = useComboboxAnchor(); + const [inputValue, setInputValue] = useState(""); - const options = useMemo( + // The static options defined in the field schema + const baseOptions = useMemo( () => Array.isArray(field.options?.values) ? field.options.values.map(normalizeOption) @@ -49,6 +53,41 @@ const EditComponent = (props: any) => { [field.options?.values], ); + // When creatable, also include any already-selected free-form values that + // aren't in the static list, so they display correctly when the form loads. + const options = useMemo(() => { + if (!creatable) return baseOptions; + + const existingValues = new Set(baseOptions.map((o: Option) => o.value)); + const selectedItems = multiple + ? (Array.isArray(value) ? value : []) + : (value != null && value !== "" ? [value] : []); + + const extra: Option[] = selectedItems + .map((v: any) => + typeof v === "object" && v !== null ? normalizeOption(v) : { value: String(v), label: String(v) } + ) + .filter((o: Option) => o.value !== "" && !existingValues.has(o.value)); + + return extra.length > 0 ? [...baseOptions, ...extra] : baseOptions; + }, [baseOptions, creatable, value, multiple]); + + // Synthetic "Create …" option shown when the typed value doesn't match anything + const createOption = useMemo