"use client" import { type ComponentProps, type ReactElement, type ReactNode, useCallback, useContext, useMemo } from "react" import { type SelectProps, type SharedSelection, Select, SelectItem } from "@heroui/react" import { type ValueOf, getEnumOptions, intParser, isNonNullable } from "deepsea-tools" import { useInputState } from "soda-hooks" import type { Field } from "soda-tanstack-form" import { getFieldProps } from "@/utils/getFieldProps" import { type EmptyValue, type GetEmptyValue, FormContext, getEmptyValue } from "./FormProvider" import type { SelectionMode } from "./FormSelect" /** enumObject 的类型 */ export type SelectOptions = Record | ([ReactNode, string | number] | readonly [ReactNode, string | number])[] /** 获取枚举值的类型 */ export type EnumValue = Options extends any[] ? Options[number][1] : ValueOf /** 获取选择器的值的类型 */ export type SelectValue< Options extends SelectOptions, Mode extends SelectionMode = "single", DisallowEmptySelection extends boolean = false, Empty extends EmptyValue = "null", > = Mode extends "multiple" ? EnumValue[] : EnumValue | (DisallowEmptySelection extends true ? never : GetEmptyValue) export interface EnumOption { label: ReactNode value: EnumValue } export interface EnumSelectPropsBase< Options extends SelectOptions, Mode extends SelectionMode = "single", DisallowEmptySelection extends boolean = false, Empty extends EmptyValue = "null", Value = SelectValue, > extends Omit< ComponentProps>>, "items" | "selectionMode" | "disallowEmptySelection" | "children" | "selectedKeys" | "onSelectionChange" | "value" | "onValueChange" > { enumObject?: Options selectionMode?: Mode disallowEmptySelection?: DisallowEmptySelection value?: Value onValueChange?: (value: Value) => void emptyValue?: Empty component?: (props: SelectProps) => ReactElement } export interface EnumSelectProps< Options extends SelectOptions, Mode extends SelectionMode = "single", DisallowEmptySelection extends boolean = false, Empty extends EmptyValue = "null", > extends EnumSelectPropsBase {} function render | ([ReactNode, string | number] | readonly [ReactNode, string | number])[]>({ label, value, }: EnumOption) { return {label} } export function EnumSelect< Options extends SelectOptions, Mode extends SelectionMode = "single", DisallowEmptySelection extends boolean = false, Empty extends EmptyValue = "null", >({ enumObject, selectionMode, value: _value, onValueChange, emptyValue, component: Select2 = Select, ...rest }: EnumSelectProps): ReactNode { const context = useContext(FormContext) emptyValue ??= context.emptyValue as Empty type Value = SelectValue const [value, setValue] = useInputState(_value as Value) const isNumberEnum = typeof enumObject === "object" && !Array.isArray(enumObject) && typeof Object.values(enumObject).at(0) === "number" const selectedKeys = useMemo(() => (Array.isArray(value) ? value : isNonNullable(value) ? [value] : []).map(String), [value]) const onSelectionChange = useCallback( function onSelectionChange(selection: SharedSelection) { const keys = Array.from(selection).map(item => (isNumberEnum ? intParser(String(item)) : String(item)) as ValueOf) const nextValue = (selectionMode === "multiple" ? keys : (keys.at(0) ?? getEmptyValue(emptyValue))) as Value setValue(nextValue) onValueChange?.(nextValue) }, [selectionMode, onValueChange, setValue, isNumberEnum, emptyValue], ) const items = useMemo( () => (Array.isArray(enumObject) ? enumObject.map(([label, value]) => ({ label, value })) : enumObject ? getEnumOptions(enumObject) : []) as EnumOption[], [enumObject], ) return ( {render} ) } export function createEnumSelect(enumObject?: Options): EnumSelectComponent> { return function EnumSelect2( props: Omit, "enumObject">, ): ReactNode { return enumObject={enumObject} {...props} /> } as EnumSelectComponent> } export type EnumSelectComponent = < Mode extends SelectionMode = "single", DisallowEmptySelection extends boolean = false, Empty extends EmptyValue = "null", >( props: Omit, "enumObject">, ) => ReactNode export interface FieldEnumSelectProps< Options extends SelectOptions, Mode extends SelectionMode = "single", DisallowEmptySelection extends boolean = false, Empty extends EmptyValue = "null", > extends Omit, "enumObject"> { field: Field> } export type FieldEnumSelectComponent = < Mode extends SelectionMode = "single", DisallowEmptySelection extends boolean = false, Empty extends EmptyValue = "null", >( props: FieldEnumSelectProps<[ReactNode, Value][], Mode, DisallowEmptySelection, Empty>, ) => ReactNode export function createFieldEnumSelect(enumObject?: Options): FieldEnumSelectComponent> { return function EnumSelect2({ field, ...rest }: FieldEnumSelectProps): ReactNode { return enumObject={enumObject} {...getFieldProps(field)} {...rest} /> } as unknown as FieldEnumSelectComponent> }