import { computed, defineComponent, onMounted, ref, watch, type PropType, type Ref, } from 'vue' import { ArkUiConstants } from '../../composables/use-ark-ui' import useClickOutside from '../../composables/use-click-outside' import type ApiInterface from '../../types/api-interface' import type DataProviderInterface from '../../types/data-provider-interface' import defineTemplate, { DEFAULT_SHOW_ITEMS_COUNT } from './template' import type { SelectSlotParams } from './types' import { type paramsSerializationOptions } from '../../composables/use-endless-scroll-api' import { createQueryCommaString, createQueryString } from '../../utils' import './styles.scss' export function defineEnumSelect() { return defineComponent({ props: { /** * Опция копирования значения */ isWithCopy: { type: Boolean, default: false, }, api: { type: Object as PropType, default: undefined, }, dataProvider: { type: Object as PropType, default: undefined, }, appearance: { type: String as PropType<'filter'|'form'>, default: 'filter', }, resetable: { type: Boolean, default: false, }, resetOnQueryParamsChange: { type: Boolean, default: false, }, disabled: { type: Boolean, default: false, }, endpoint: { type: String, required: true, }, errors: { type: Array as PropType, default: () => [], }, itemConverter: { type: Function as PropType<(item: any) => T>, default: (i: any) => i, }, itemSlot: { type: Function as PropType<(item: T, params: SelectSlotParams) => JSX.Element>, }, modelValue: { type: Object as PropType, default: undefined, }, onValueChange: { type: Function as PropType<(v: T|undefined) => void>, default: () => () => { // do nothing }, }, params: { type: Object as PropType<{ [code: string] : string|string[] }>, default: () => ({}), }, placeholder: { type: String, default: '', }, preselected: { type: Boolean, default: false, }, searchKey: { type: String, default: 'name', }, size: { type: String as PropType<'M'|'S'>, }, title: { type: String, }, paramsSerialization: { type: String as PropType, default: 'default', }, additionalItems: { type: Array as PropType, }, showItemsCount: { type: Number, default: DEFAULT_SHOW_ITEMS_COUNT, }, showDescriptionId: { type: Boolean, default: false, }, }, setup(props) { const api = props.api || ArkUiConstants.api if (!api && !props.dataProvider) throw new Error('API is not defined') const isOpened = ref(false) const triggerDown = ref(null) as unknown as Ref const triggerUp = ref(null) as unknown as Ref const canBeLoaded = ref(false) const root = ref(null) as unknown as Ref const select = ref(null) as unknown as Ref const isLoadingPrev = ref(false) const isLoadingNext = ref(false) const value = computed(() => props.modelValue as T) const queryParams = computed((): typeof props.params => { const params = { ...props.params } // Де-структуризация ломает реактивность return params }) const clear = () => { if (props.onValueChange) props.onValueChange(undefined) } const items: Ref = ref([]) const checkParamsSerialization = async (): Promise => { if (!api) throw new Error('API is not defined') let resp switch (props.paramsSerialization) { case 'default': resp = (await api.get(props.endpoint, { params: queryParams.value })).data break case 'custom': resp = (await api.get(props.endpoint + createQueryString(queryParams.value))).data break case 'comma': resp = (await api.get(props.endpoint + createQueryCommaString(queryParams.value))).data break default: break } return resp } const load = async () => { const response = props.dataProvider ? await props.dataProvider.get({ params: queryParams.value }) : await checkParamsSerialization() items.value = response.map((i: any) => props.itemConverter(i)) } if (props.preselected) { onMounted(async () => { await load() canBeLoaded.value = true if (props.onValueChange) props.onValueChange(items.value[0]) }) } watch(() => props.params, () => { if (props.resetOnQueryParamsChange) clear() // мгновенная перезагрузка происходит только в том случае, если селект раскрыт, иначе она будет выполена отложенно при раскрытии if (isOpened.value) { isLoadingNext.value = true isLoadingPrev.value = true load().finally(() => { isLoadingNext.value = false isLoadingPrev.value = false }) return } items.value = [] }) watch(isOpened, async () => { if (isOpened.value && items.value.length === 0) { isLoadingNext.value = true await load() isLoadingNext.value = false canBeLoaded.value = true } }) useClickOutside(select, () => { isOpened.value = false }) const toggleOpened = () => { isOpened.value = !isOpened.value } const toggleItem = (item: T) => { if (value.value?.id === item.id) { if (props.onValueChange) props.onValueChange(undefined) } else if (props.onValueChange) props.onValueChange(item) toggleOpened() } const isSelected = (item: T) => value.value?.id === item.id const SelectTemplate = defineTemplate() return (): JSX.Element => { const templateProps = { clear, isWithCopy: props.isWithCopy, disabled: props.disabled, errors: props.errors, isLoadingNext: isLoadingNext.value, isLoadingPrev: isLoadingPrev.value, isOpened: isOpened.value, isSelected, items: items.value, itemSlot: props.itemSlot, modelValue: value.value ? [value.value] : [], placeholder: props.placeholder, resetable: props.resetable, root, select, title: props.title, toggleItem, toggleOpened, topItems: [], triggerUp, triggerDown, isSingle: true, appearance: props.appearance, additionalItems: props.additionalItems, size: props.size, showItemsCount: props.showItemsCount, showDescriptionId: props.showDescriptionId, } return } }, }) } export default defineEnumSelect<{ id: string, name: string }>()