import { computed, defineComponent, onMounted, type PropType, ref, type ComponentPublicInstance, } from 'vue'; import type { BCMSSelectOption } from '../../../types'; import BCMSIcon from '../../icon'; import InputWrapper from '../_input'; import { DefaultComponentProps } from '../../_default'; import BCMSSelectList from './list'; const component = defineComponent({ props: { ...DefaultComponentProps, label: String, placeholder: String, invalidText: String, helperText: String, disabled: { type: Boolean, default: false, }, selected: { type: String, default: '', }, options: { type: Array as PropType, default: () => [], }, showSearch: { type: Boolean, default: false, }, propPath: String, }, emits: { change: (_option: BCMSSelectOption) => { return true; }, }, setup(props, ctx) { const bcmsDropdownList = ref(null); const search = ref(''); const searchInput = ref(null); const toggler = ref(null); const position = ref<{ bottom: number }>({ bottom: -10, }); const container = ref(null); const filteredOptions = computed(() => { if (!props.showSearch) { return props.options; } return props.options.filter((option) => search.value ? option.value.toLowerCase().includes(search.value) || option.label.toLowerCase().includes(search.value) : true, ); }); const isDropdownActive = ref(false); const logic = { handleSearchInput(event: Event) { const element = event.target as HTMLInputElement; if (!element) { return; } const value = element.value; if (!props.showSearch) { logic.scrollSearchedElementIntoView(value); } else { search.value = value.toLowerCase(); } }, scrollSearchedElementIntoView(value: string) { if (!bcmsDropdownList.value) { return; } const options = Array.from( bcmsDropdownList.value.$el.querySelectorAll('li'), ) as HTMLLIElement[]; if (!options) { return; } options.forEach((option) => { const optionText = option.textContent?.toLowerCase(); if (optionText && optionText.includes(value.toLowerCase())) { option.scrollIntoView({ behavior: 'smooth', block: 'start', }); } }); if (!value) { bcmsDropdownList.value.$el.scrollTo({ top: 0, behavior: 'smooth', }); } }, toggleDropdown() { isDropdownActive.value = !isDropdownActive.value; if (isDropdownActive.value && !props.showSearch) { window.addEventListener('keydown', eventListeners); if (searchInput.value) { searchInput.value.focus(); } } else { window.removeEventListener('keydown', eventListeners); search.value = ''; } }, getPlaceholderText(_selected: string) { if (!_selected) { return props.placeholder; } const selectedOption = props.options.find((e) => e.value === _selected); if (!selectedOption) { return props.placeholder; } return selectedOption.label ? selectedOption.label : selectedOption.value; }, isItemSelected(item: BCMSSelectOption) { return item.value === props.selected; }, }; function eventListeners(event: KeyboardEvent) { const dropDown = { root: bcmsDropdownList.value?.$el, active: (bcmsDropdownList.value?.$el.querySelector( 'li:focus', ) as HTMLLIElement) || (bcmsDropdownList.value?.$el.querySelector( 'li.selected', ) as HTMLLIElement), firstItem: bcmsDropdownList.value?.$el.querySelector( 'li:first-child', ) as HTMLLIElement, lastItem: bcmsDropdownList.value?.$el.querySelector( 'li:last-child', ) as HTMLLIElement, }; switch (event.key) { case 'Escape': // 'ESC' - Close dropdown event.preventDefault(); logic.toggleDropdown(); break; case 'ArrowUp': // 'ARROW UP' - Move up event.preventDefault(); if (!dropDown.active || !dropDown.active.previousElementSibling) { if (dropDown.lastItem) { dropDown.lastItem.focus(); } } else { const prevSibl = dropDown.active.previousSibling as HTMLLIElement; prevSibl.focus(); } break; case 'ArrowDown': // 'ARROW DOWN' - Move down event.preventDefault(); if (!dropDown.active || !dropDown.active.nextElementSibling) { if (dropDown.firstItem) { dropDown.firstItem.focus(); } } else { const nextSibling = dropDown.active.nextSibling as HTMLLIElement; nextSibling.focus(); } break; default: break; } } onMounted(() => { if (searchInput.value && props.showSearch) { searchInput.value.focus(); } }); return () => { return (
{!props.showSearch && ( )} {(isDropdownActive.value || props.showSearch) && !props.disabled && !props.showSearch ? (
{ isDropdownActive.value = false; }} style={`bottom: ${position.value.bottom}px;`} > { ctx.emit('change', payload); isDropdownActive.value = false; }} onHide={() => { isDropdownActive.value = false; }} />
) : (isDropdownActive.value || props.showSearch) && !props.disabled && props.showSearch ? ( { ctx.emit('change', payload); isDropdownActive.value = false; }} v-clickOutside={() => { isDropdownActive.value = false; }} /> ) : ( '' )}
); }; }, }); export default component;