UNPKG

@coreui/vue-pro

Version:

UI Components Library for Vue.js

259 lines (229 loc) 8.71 kB
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' const CMultiSelectOptions = defineComponent({ name: 'CMultiSelectOptions', props: { loading: Boolean, options: { type: Array as PropType<(Option | OptionsGroup)[]>, default: () => [], }, optionsGroupsSelectable: Boolean, optionsGroupsStyle: { type: String, default: 'checkbox', validator: (value: string) => { return ['checkbox', 'text'].includes(value) }, }, optionsMaxHeight: { type: [Number, String], default: 'auto', }, optionsStyle: { type: String, default: 'checkbox', validator: (value: string) => { return ['checkbox', 'text'].includes(value) }, }, scopedSlots: Object, searchNoResultsLabel: { type: String, default: 'no items', }, selected: { type: Array as PropType<Option[]>, default: () => [], }, virtualScroller: Boolean, visibleItems: { type: Number, default: 10, }, }, emits: ['groupClick', 'optionClick'], setup(props, { emit }) { const handleHomeEndKeyDown = (event: KeyboardEvent) => { if (event.key !== 'Home' && event.key !== 'End') { return } event.preventDefault() const target = event.target as HTMLElement const container = target.closest('.form-multi-select-options') as HTMLElement | null if (!container) { return } const first = event.key === 'Home' const focusEdgeOption = () => { const options = container.querySelectorAll<HTMLElement>( '.form-multi-select-option:not(.disabled)' ) if (options.length > 0) { options[first ? 0 : options.length - 1].focus() } } if (props.virtualScroller) { // The virtualized viewport only mounts a window of options — scroll it to // the edge so the true first/last options render, then focus after the // window has re-rendered (scroll event → state update → commit). container.scrollTop = first ? 0 : container.scrollHeight requestAnimationFrame(() => requestAnimationFrame(focusEdgeOption)) return } focusEdgeOption() } const handleKeyDown = (event: KeyboardEvent, option: Option) => { if (event.code === 'Space' || event.key === 'Enter') { event.preventDefault() handleOptionClick && handleOptionClick(option) return } if (event.key === 'Down' || event.key === 'ArrowDown') { event.preventDefault() const target = event.target as HTMLElement const next = getNextSibling(target, '.form-multi-select-option') next && (next as HTMLElement).focus() } if (event.key === 'Up' || event.key === 'ArrowUp') { event.preventDefault() const target = event.target as HTMLElement const prev = getPreviousSibling(target, '.form-multi-select-option') prev && (prev as HTMLElement).focus() } handleHomeEndKeyDown(event) } const handleGroupKeyDown = (event: KeyboardEvent, group: OptionsGroup) => { if (event.code === 'Space' || event.key === 'Enter') { event.preventDefault() emit('groupClick', group) return } if (event.key === 'Down' || event.key === 'ArrowDown') { event.preventDefault() const next = getNextSibling(event.target as HTMLElement, '.form-multi-select-option') next && (next as HTMLElement).focus() } if (event.key === 'Up' || event.key === 'ArrowUp') { event.preventDefault() const prev = getPreviousSibling(event.target as HTMLElement, '.form-multi-select-option') prev && (prev as HTMLElement).focus() } handleHomeEndKeyDown(event) } const getGroupCheckboxState = (group: OptionsGroup) => { const groupOptions = (group.options ?? []).filter((option) => !option.disabled) const selectedCount = groupOptions.filter((option) => props.selected.some((_option) => _option.value === option.value) ).length if (groupOptions.length > 0 && selectedCount >= groupOptions.length) { return 'all' } return selectedCount === 0 ? 'none' : 'indeterminate' } const handleOptionClick = (option: Option) => { emit('optionClick', option as Option) } const handleGroupClick = (group: OptionsGroup) => { emit('groupClick', group as OptionsGroup) } // TODO: find solution how to remove any const createOptions = (options: (Option | OptionsGroup)[]) => options.length > 0 ? options.map((option: Option | OptionsGroup) => 'value' in option ? h( 'div', { class: [ 'form-multi-select-option', { 'form-multi-select-option-with-checkbox': props.optionsStyle === 'checkbox', 'form-multi-selected': props.selected.some( (_option) => _option.value === option.value ), disabled: option.disabled, }, ], onClick: () => handleOptionClick(option as Option), onKeydown: (event: KeyboardEvent) => handleKeyDown(event, option as Option), tabindex: 0, role: 'option', 'aria-selected': props.selected.some( (_option) => _option.value === option.value ), }, props.scopedSlots && props.scopedSlots['options'] ? h(props.scopedSlots['options'], { option: option }) : option.label ) : [ h( 'div', { class: [ 'form-multi-select-optgroup-label', { 'form-multi-select-optgroup-label-with-checkbox': props.optionsGroupsSelectable && props.optionsGroupsStyle === 'checkbox', 'form-multi-selected': props.optionsGroupsSelectable && props.optionsGroupsStyle === 'checkbox' && getGroupCheckboxState(option) === 'all', 'form-multi-select-indeterminate': props.optionsGroupsSelectable && props.optionsGroupsStyle === 'checkbox' && getGroupCheckboxState(option) === 'indeterminate', }, ], ...(props.optionsGroupsSelectable && props.optionsGroupsStyle === 'checkbox' && { onClick: () => handleGroupClick(option as OptionsGroup), onKeydown: (event: KeyboardEvent) => handleGroupKeyDown(event, option as OptionsGroup), tabindex: 0, role: 'button', }), }, props.scopedSlots && props.scopedSlots['options-groups'] ? h(props.scopedSlots['options-groups'], { option: option }) : option.label ), ] ) : h( 'div', { class: 'form-multi-select-options-empty', role: 'status' }, props.searchNoResultsLabel ) return () => [ props.virtualScroller ? h( CVirtualScroller, { class: 'form-multi-select-options', visibleItems: props.visibleItems, }, { default: () => createOptions(props.options), } ) : h( 'div', { class: 'form-multi-select-options', ...(props.optionsMaxHeight !== 'auto' && { style: { maxHeight: props.optionsMaxHeight, overflow: 'scroll' }, }), }, createOptions(props.options) ), props.loading && h(CElementCover), ] }, }) export { CMultiSelectOptions }