@coreui/vue-pro
Version:
UI Components Library for Vue.js
145 lines (130 loc) • 4.62 kB
text/typescript
import { defineComponent, h, PropType } from 'vue'
import { CElementCover } from '../element-cover'
import { CVirtualScroller } from '../virtual-scroller'
import { getNextSibling, getPreviousSibling } from '../../utils'
import type { Option, OptionsGroup } from './types'
import { getOptionLabel, highlightSubstring, isOptionDisabled, isOptionSelected } from './utils'
const CAutocompleteOptions = defineComponent({
name: 'CAutocompleteOptions',
props: {
highlightOptionsOnSearch: Boolean,
loading: Boolean,
options: {
type: Array as PropType<(Option | OptionsGroup)[]>,
required: true,
},
optionsMaxHeight: [Number, String] as PropType<number | string>,
scopedSlots: Object,
searchNoResultsLabel: [Boolean, String] as PropType<boolean | string>,
searchValue: String,
selected: [Object, String, null] as PropType<Option | null>,
virtualScroller: Boolean,
visible: Boolean,
visibleItems: {
type: Number,
default: 10,
},
},
emits: ['optionClick'],
setup(props, { emit }) {
const handleKeyDown = (event: KeyboardEvent, option: Option) => {
if (event.code === 'Space' || event.key === 'Enter') {
event.preventDefault()
emit('optionClick', option)
}
if (event.key === 'Down' || event.key === 'ArrowDown') {
event.preventDefault()
const target = event.target as HTMLElement
const next = getNextSibling(
target,
'.autocomplete-option:not(.disabled):not(:disabled)'
) as HTMLElement | null
if (next) {
next.focus()
}
}
if (event.key === 'Up' || event.key === 'ArrowUp') {
event.preventDefault()
const target = event.target as HTMLElement
const prev = getPreviousSibling(
target,
'.autocomplete-option:not(.disabled):not(:disabled)'
) as HTMLElement | null
if (prev) {
prev.focus()
}
}
}
const createOption = (option: Option, index: number) =>
h(
'div',
{
class: [
'autocomplete-option',
{
disabled: isOptionDisabled(option),
selected: isOptionSelected(option, props.selected || null),
},
],
key: index,
onClick: () => emit('optionClick', option),
onKeydown: (event: KeyboardEvent) => handleKeyDown(event, option),
tabindex: 0,
...(props.highlightOptionsOnSearch &&
!props.scopedSlots?.['options'] && {
innerHTML: highlightSubstring(getOptionLabel(option), props.searchValue),
}),
},
!props.highlightOptionsOnSearch
? props.scopedSlots && props.scopedSlots['options']
? h(props.scopedSlots['options'], { option: option })
: getOptionLabel(option)
: undefined
)
const createOptions = (options: (Option | OptionsGroup)[]) => {
if (options.length === 0 && props.searchNoResultsLabel) {
return h('div', { class: 'autocomplete-options-empty' }, props.searchNoResultsLabel)
}
return options.map((option: Option | OptionsGroup, index: number) => {
if (typeof option !== 'string' && 'options' in option) {
return h('div', { key: index }, [
h('div', { class: 'autocomplete-optgroup-label' }, [
props.scopedSlots && props.scopedSlots['options-groups']
? h(props.scopedSlots['options-groups'], { option: option })
: option.label,
]),
...(option.options?.map((opt: Option, idx: number) => createOption(opt, idx)) || []),
])
}
return createOption(option as Option, index)
})
}
return () => [
props.visible && props.virtualScroller && props.options.length > 0
? h(
CVirtualScroller,
{
class: 'autocomplete-options',
visibleItems: props.visibleItems,
role: 'listbox',
},
{
default: () => createOptions(props.options),
}
)
: h(
'div',
{
class: 'autocomplete-options',
...(props.optionsMaxHeight !== 'auto' && {
style: { maxHeight: props.optionsMaxHeight, overflow: 'scroll' },
}),
role: 'listbox',
},
createOptions(props.options)
),
props.loading && h(CElementCover),
]
},
})
export { CAutocompleteOptions }