'use client'; import * as React from 'react'; import { type MouseEventHandler } from 'react'; import { classNames } from '@vkontakte/vkjs'; import { useExternRef } from '../../hooks/useExternRef'; import { useGlobalOnEventOutside } from '../../hooks/useGlobalOnClickOutside'; import { useMergeProps } from '../../hooks/useMergeProps'; import { Keys } from '../../lib/accessibility'; import type { Placement } from '../../lib/floating'; import { defaultFilterFn } from '../../lib/select'; import { ChipsInputBase } from '../ChipsInputBase/ChipsInputBase'; import { getNewOptionDataDefault, getOptionLabelDefault, getOptionValueDefault, renderChipDefault, } from '../ChipsInputBase/constants'; import type { ChipOption, ChipsInputBaseProps } from '../ChipsInputBase/types'; import { CustomSelectDropdown, type CustomSelectDropdownProps, } from '../CustomSelectDropdown/CustomSelectDropdown'; import { CustomSelectOption, type CustomSelectOptionProps, } from '../CustomSelectOption/CustomSelectOption'; import { DropdownIcon } from '../DropdownIcon/DropdownIcon'; import type { FormFieldProps } from '../FormField/FormField'; import { Footnote } from '../Typography/Footnote/Footnote'; import { DEFAULT_EMPTY_TEXT, DEFAULT_SELECTED_BEHAVIOR, FOCUS_ACTION_NEXT, FOCUS_ACTION_PREV, isCreateNewOptionPreset, isEmptyOptionPreset, isNotServicePreset, renderOptionDefault, } from './constants'; import type { FocusActionType, OptionPreset } from './types'; import { useChipsSelect, type UseChipsSelectProps } from './useChipsSelect'; import styles from './ChipsSelect.module.css'; const findIndexAfter = ( options: Array> = [], startIndex = -1, ) => { if (startIndex >= options.length - 1) { return -1; } return options.findIndex( (option, i) => i > startIndex && (!isNotServicePreset(option) || !option.disabled), ); }; const findIndexBefore = ( options: Array> = [], endIndex: number = options.length, ) => { let result = -1; if (endIndex <= 0) { return result; } for (let i = endIndex - 1; i >= 0; i--) { let option = options[i]; if (!isNotServicePreset(option) || !option.disabled) { result = i; break; } } return result; }; export interface ChipsSelectProps extends ChipsInputBaseProps, UseChipsSelectProps, Pick, Pick { /** * Расположение выпадающего списка. */ placement?: 'top' | 'bottom'; /** * Отрисовка Spinner вместо списка опций в выпадающем списке. */ fetching?: boolean; /** * Закрытие выпадающего списка после выбора элемента. */ closeAfterSelect?: boolean; /** * Ширина раскрывающегося списка зависит от контента. */ dropdownAutoWidth?: boolean; /** * Принудительно использовать портал. */ forceDropdownPortal?: boolean; /** * Передает атрибут `data-testid` для дропдауна. */ dropdownTestId?: string; /** * Иконка раскрывающегося списка. */ icon?: React.ReactNode; /** * Добавляет значение в список на событие `onBlur` (использовать вместе с `creatable`). */ addOnBlur?: boolean; /** * Отключает максимальную высоту по умолчанию. */ noMaxHeight?: boolean; /** * Функция для отрисовки кастомной опции в выпадающем списке. */ renderOption?: (props: CustomSelectOptionProps, option: O) => React.ReactNode; /** * Рендер-проп для кастомного рендера содержимого дропдауна. * В `defaultDropdownContent` содержится список опций. */ renderDropdown?: ({ defaultDropdownContent, }: { defaultDropdownContent: React.ReactNode; }) => React.ReactNode; /** * Событие срабатывающее перед `onChange`. */ onChangeStart?: (event: React.MouseEvent | React.KeyboardEvent, option: O) => void; /** * Отступ от выпадающего списка. */ dropdownOffsetDistance?: number; /** * Если `true`, то справа будет отображаться кнопка для очистки значения. */ allowClearButton?: boolean; } /** * @see https://vkui.io/components/chips-select */ export const ChipsSelect =