import { type CSSProperties, defineComponent, onMounted, onUnmounted, type PropType, ref, type Ref, Teleport, } from 'vue'; import * as uuid from 'uuid'; import type { BCMSSelectOption } from '../../../types'; const component = defineComponent({ props: { inputRef: Object as PropType>, options: { type: Array as PropType, required: true, }, showSearch: { type: Boolean, required: false, default: false, }, invalidText: { type: String, required: false, default: '', }, selected: { type: String, required: false, default: '', }, }, emits: { change: (_option: BCMSSelectOption) => { return true; }, hide: () => { return true; }, }, setup(props, ctx) { const scrollerId = uuid.v4(); const container = ref(); const positionStyles = ref({ top: '0 !important', left: '0 !important', width: '100px !important', }); const logic = { isItemSelected(option: BCMSSelectOption) { return option.value === props.selected; }, selectOption(option: BCMSSelectOption) { if (option.value === props.selected) { ctx.emit('change', { label: '', value: '' }); } else { ctx.emit('change', option); } }, updatePosition() { if (props.inputRef) { const parentEl = props.inputRef.value; if (parentEl && container.value) { const bb = parentEl.getBoundingClientRect(); if ( bb.bottom + 10 + container.value.offsetHeight > window.innerHeight ) { positionStyles.value.top = `${ bb.top - 10 - container.value.offsetHeight }px !important`; } else { positionStyles.value.top = `${bb.bottom + 10}px !important`; } positionStyles.value.width = `${parentEl.clientWidth}px !important`; positionStyles.value.left = `${bb.left}px !important`; } } }, findScrollableEls(from: HTMLElement) { if (from.scrollHeight > from.offsetHeight) { scrollableParents.push(from); } if (from.parentElement) { logic.findScrollableEls(from.parentElement); } }, }; logic.updatePosition(); const scrollableParents: HTMLElement[] = []; const obs = new IntersectionObserver( (entries) => { entries.forEach((entry) => { if (!entry.isIntersecting) { ctx.emit('hide'); } }); }, { threshold: 1, } ); const scrollToSelectedOptionElement = () => { // If there is a selected element, scroll to it // but keep it on the second place // for better UX if (container.value && props.selected) { const selectedElement = container.value.querySelector( `[data-bcms-value="${props.selected}"]` ); if (selectedElement) { selectedElement.scrollIntoView(); container.value.scrollBy({ top: -selectedElement.clientHeight, }); } } }; onMounted(() => { if (props.inputRef && props.inputRef.value) { obs.observe(props.inputRef.value); logic.findScrollableEls(props.inputRef.value); for (let i = 0; i < scrollableParents.length; i++) { const element = scrollableParents[i]; element.addEventListener('scroll', logic.updatePosition); } } logic.updatePosition(); scrollToSelectedOptionElement(); }); onUnmounted(() => { if (props.inputRef && props.inputRef.value) { obs.unobserve(props.inputRef.value); } for (let i = 0; i < scrollableParents.length; i++) { const element = scrollableParents[i]; element.removeEventListener('scroll', logic.updatePosition); } }); return () => { return (
    {props.options.map((option) => (
  • { if (event.key === 'Enter') { logic.selectOption(option); } }} onClick={() => { logic.selectOption(option); }} > {option.image ? ( Flag ) : ( '' )} {option.label || option.value}
  • ))}
); }; }, }); export default component;