diff --git a/Form/Core/AutocompleteSelect.js b/Form/Core/AutocompleteSelect.js
deleted file mode 100644
index f12fd59..0000000
--- a/Form/Core/AutocompleteSelect.js
+++ /dev/null
@@ -1,128 +0,0 @@
-import { Combobox } from "@headlessui/react";
-
-import { DropDownItem } from "@shared/nej-react-components/Parts/DropDown";
-import { useEffect, useRef, useState } from "react";
-
-import tw, { styled } from "twin.macro";
-import "styled-components/macro";
-import { useField } from "@shared/nej-react-components/Parts/Input";
-
-export function AutocompleteSelect({ title, titleProps = null, data, nullable = false, renderOption = undefined, onScrollEnd = undefined, onQueryChange = undefined, ...props }) {
- const [field, meta, helpers] = useField(props);
-
- const [query, setQuery] = useState(field.value ?? "");
- const [internalData, setInternalData] = useState([]);
- const sentinelRef = useRef(null);
-
- useEffect(() => {
- if (!onScrollEnd || !sentinelRef.current) return;
- const observer = new IntersectionObserver(
- (entries) => { if (entries[0].isIntersecting) onScrollEnd(); },
- { threshold: 0.1 }
- );
- observer.observe(sentinelRef.current);
- return () => observer.disconnect();
- }, [onScrollEnd]);
-
- useEffect(() => {
- (async () => {
- if (typeof data === "function") {
- //if data is async function await it
- setInternalData(await data(query));
- } else if (Array.isArray(data)) {
- // When renderOption is provided the caller manages filtering externally, just show data as-is
- if (renderOption) {
- setInternalData(data);
- } else {
- setInternalData(
- query === ""
- ? data
- : data.filter((item) => {
- if (typeof item === "string")
- return item.toLowerCase().includes(query.toLowerCase());
-
- return item.label.toLowerCase().includes(query.toLowerCase());
- })
- );
- }
- } else if (typeof data === "object") {
- // TypeScript enum objects — convert values to { value, label } then filter
- const enumItems = Object.values(data).map((value) => ({ value, label: value }));
- setInternalData(
- !renderOption && query !== ""
- ? enumItems.filter((item) => String(item.label).toLowerCase().includes(query.toLowerCase()))
- : enumItems
- );
- }
- else {
- console.error("data is not an array or function :c");
- setInternalData([]);
- }
- })();
- }, [query, data]);
-
- return (
- <>
-
- typeof item === "string"
- ? item == field.value
- : item.value === field.value
- )[0]
- : null
- }
- onChange={(value) => {
- // If value is an object (from internalData lookup), store only the primitive .value
- const stored = value != null && typeof value === "object" ? value.value : value;
- field.onChange({ target: { value: stored, name: field.name } });
- }}
- nullable={nullable}
- >
-
- {title && (
-
- )}
-
- { setQuery(event.target.value); onQueryChange?.(event.target.value); }}
- displayValue={(val) => (typeof val === "string" ? val : val?.label)}
- />
-
-
-
- {internalData?.map((val) => (
-
- {renderOption
- ? (state) => renderOption(val, state)
- : {typeof val === "string" ? val : val.label}
- }
-
- ))}
- {onScrollEnd && }
-
-
-
-
- {meta?.touched && meta.error ? (
-
{meta.error}
- ) : null}
- >
- );
-}
diff --git a/Form/Core/AutocompleteSelect.tsx b/Form/Core/AutocompleteSelect.tsx
new file mode 100644
index 0000000..738db04
--- /dev/null
+++ b/Form/Core/AutocompleteSelect.tsx
@@ -0,0 +1,143 @@
+import { Combobox } from "@headlessui/react";
+
+import { DropDownItem } from "@shared/nej-react-components/Parts/DropDown";
+import { useEffect, useRef, useState } from "react";
+
+import tw from "twin.macro";
+import "styled-components/macro";
+import { useField } from "@shared/nej-react-components/Parts/Input";
+import { FieldHookConfig } from "formik";
+
+type Option = string | { value: T, label: string };
+type RenderOption = (item: Option, state: { active: boolean, selected: boolean }) => JSX.Element;
+
+export function AutocompleteSelect({ title, titleProps = null, data, nullable = false, renderOption = undefined, onScrollEnd = undefined, onQueryChange = undefined, ...props }:
+ FieldHookConfig &
+ ({ title: string, titleProps?: any, data: Option[] | ((query: string) => Promise