2022-09-06 12:03:37 +00:00
< template >
2024-02-11 23:48:09 +00:00
< div ref = "container" :class ="cx('root')" @click ="onContainerClick" v-bind ="ptmi('root')" >
2023-07-03 22:20:35 +00:00
< div class = "p-hidden-accessible" v-bind ="ptm('hiddenInputWrapper')" :data-p-hidden-accessible="true" >
2022-09-14 11:26:01 +00:00
< input
ref = "focusInput"
: id = "inputId"
type = "text"
readonly
: disabled = "disabled"
: placeholder = "placeholder"
: tabindex = "!disabled ? tabindex : -1"
role = "combobox"
: aria - label = "ariaLabel"
: aria - labelledby = "ariaLabelledby"
aria - haspopup = "listbox"
: aria - expanded = "overlayVisible"
: aria - controls = "id + '_list'"
: aria - activedescendant = "focused ? focusedOptionId : undefined"
@ focus = "onFocus"
@ blur = "onBlur"
@ keydown = "onKeyDown"
2023-07-07 12:57:51 +00:00
v - bind = "{ ...inputProps, ...ptm('hiddenInput') }"
2022-09-14 11:26:01 +00:00
/ >
2022-09-06 12:03:37 +00:00
< / div >
2023-05-25 14:01:28 +00:00
< div :class ="cx('labelContainer')" v-bind ="ptm('labelContainer')" >
< div :class ="cx('label')" v-bind ="ptm('label')" >
2022-09-06 12:03:37 +00:00
< slot name = "value" :value ="modelValue" :placeholder ="placeholder" >
< template v-if ="display === 'comma'" >
2022-09-14 11:26:01 +00:00
{ { label || 'empty' } }
2022-09-06 12:03:37 +00:00
< / template >
< template v -else -if = " display = = = ' chip ' " >
2023-05-25 14:01:28 +00:00
< div v-for ="item of chipSelectedItems" :key="getLabelByValue(item)" :class="cx('token')" v-bind="ptm('token')" >
2022-09-06 12:03:37 +00:00
< slot name = "chip" :value ="item" >
2023-05-25 14:01:28 +00:00
< span :class ="cx('tokenLabel')" v-bind ="ptm('tokenLabel')" > {{ getLabelByValue ( item ) }} < / span >
2022-09-06 12:03:37 +00:00
< / slot >
2023-10-31 17:21:47 +00:00
< slot v-if ="!disabled" name="removetokenicon" :class="cx('removeTokenIcon')" :item="item" :onClick="(event) => removeOption(event, item)" :removeCallback="(event) => removeOption(event, item)" >
2023-05-25 14:01:28 +00:00
< span v -if = " removeTokenIcon " : class = "[cx('removeTokenIcon'), removeTokenIcon]" @ click.stop = " removeOption ( $ event , item ) " v -bind = " ptm ( ' removeTokenIcon ' ) " / >
< TimesCircleIcon v -else :class ="cx('removeTokenIcon')" @ click.stop = " removeOption ( $ event , item ) " v -bind = " ptm ( ' removeTokenIcon ' ) " / >
2023-04-04 12:41:18 +00:00
< / slot >
2022-09-06 12:03:37 +00:00
< / div >
2022-09-14 11:26:01 +00:00
< template v-if ="!modelValue || modelValue.length === 0" > {{ placeholder | | ' empty ' }} < / template >
2022-09-06 12:03:37 +00:00
< / template >
< / slot >
< / div >
< / div >
2023-07-13 12:23:46 +00:00
< div :class ="cx('trigger')" v-bind ="ptm('trigger')" >
2023-07-06 12:28:09 +00:00
< slot v-if ="loading" name="loadingicon" :class="cx('loadingIcon')" >
< span v -if = " loadingIcon " : class = "[cx('loadingIcon'), 'pi-spin', loadingIcon]" aria -hidden = " true " v -bind = " ptm ( ' loadingIcon ' ) " / >
< SpinnerIcon v -else :class ="cx('loadingIcon')" spin aria -hidden = " true " v -bind = " ptm ( ' loadingIcon ' ) " / >
2023-04-18 12:09:35 +00:00
< / slot >
2023-05-25 14:01:28 +00:00
< slot v -else name = "dropdownicon" :class ="cx('dropdownIcon')" >
< component : is = "dropdownIcon ? 'span' : 'ChevronDownIcon'" : class = "[cx('dropdownIcon'), dropdownIcon]" aria -hidden = " true " v -bind = " ptm ( ' dropdownIcon ' ) " / >
2023-04-18 12:09:35 +00:00
< / slot >
2022-09-06 12:03:37 +00:00
< / div >
< Portal :appendTo ="appendTo" >
2023-08-02 12:01:41 +00:00
< transition name = "p-connected-overlay" @enter ="onOverlayEnter" @after-enter ="onOverlayAfterEnter" @leave ="onOverlayLeave" @after-leave ="onOverlayAfterLeave" v-bind ="ptm('transition')" >
2023-05-25 14:01:28 +00:00
< div v-if ="overlayVisible" :ref="overlayRef" :style="panelStyle" :class="[cx('panel'), panelClass]" @click="onOverlayClick" @keydown="onOverlayKeyDown" v-bind="{ ...panelProps, ...ptm('panel') }" >
< span
ref = "firstHiddenFocusableElementOnOverlay"
role = "presentation"
aria - hidden = "true"
2023-07-03 22:20:35 +00:00
class = "p-hidden-accessible p-hidden-focusable"
2023-05-25 14:01:28 +00:00
: tabindex = "0"
@ focus = "onFirstHiddenFocus"
v - bind = "ptm('hiddenFirstFocusableEl')"
: data - p - hidden - accessible = "true"
: data - p - hidden - focusable = "true"
> < / span >
2022-09-06 12:03:37 +00:00
< slot name = "header" :value ="modelValue" :options ="visibleOptions" > < / slot >
2023-05-25 14:01:28 +00:00
< div v-if ="(showToggleAll && selectionLimit == null) || filter" :class="cx('header')" v-bind="ptm('header')" >
2024-01-14 14:09:03 +00:00
< Checkbox
v - if = "showToggleAll && selectionLimit == null"
: modelValue = "allSelected"
: binary = "true"
: disabled = "disabled"
2024-02-02 11:46:26 +00:00
: variant = "variant"
2024-01-14 14:09:03 +00:00
: aria - label = "toggleAllAriaLabel"
@ change = "onToggleAll"
: unstyled = "unstyled"
: pt = "getHeaderCheckboxPTOptions('headerCheckbox')"
>
2024-01-14 13:38:51 +00:00
< template # icon = "slotProps" >
< component v -if = " $ slots.headercheckboxicon " :is ="$slots.headercheckboxicon" :checked ="slotProps.checked" :class ="slotProps.class" / >
< component v -else -if = " slotProps.checked " : is = "checkboxIcon ? 'span' : 'CheckIcon'" : class = "[slotProps.class, { [checkboxIcon]: slotProps.checked }]" v -bind = " getHeaderCheckboxPTOptions ( ' headerCheckbox.icon ' ) " / >
< / template >
< / Checkbox >
2023-05-25 14:01:28 +00:00
< div v-if ="filter" :class="cx('filterContainer')" v-bind="ptm('filterContainer')" >
2022-09-14 11:26:01 +00:00
< input
ref = "filterInput"
type = "text"
: value = "filterValue"
2023-05-11 13:05:20 +00:00
@ vue : mounted = "onFilterUpdated"
2024-02-07 12:59:20 +00:00
@ vue : updated = "onFilterUpdated"
2023-05-25 14:01:28 +00:00
: class = "cx('filterInput')"
2022-09-14 11:26:01 +00:00
: placeholder = "filterPlaceholder"
role = "searchbox"
autocomplete = "off"
: aria - owns = "id + '_list'"
: aria - activedescendant = "focusedOptionId"
@ keydown = "onFilterKeyDown"
@ blur = "onFilterBlur"
@ input = "onFilterChange"
2023-05-08 07:00:16 +00:00
v - bind = "{ ...filterInputProps, ...ptm('filterInput') }"
2022-09-14 11:26:01 +00:00
/ >
2023-05-25 14:01:28 +00:00
< slot name = "filtericon" :class ="cx('filterIcon')" >
< component : is = "filterIcon ? 'span' : 'SearchIcon'" : class = "[cx('filterIcon'), filterIcon]" v -bind = " ptm ( ' filterIcon ' ) " / >
2023-04-04 12:41:18 +00:00
< / slot >
2022-09-06 12:03:37 +00:00
< / div >
2023-07-03 22:20:35 +00:00
< span v-if ="filter" role="status" aria-live="polite" class="p-hidden-accessible" v-bind="ptm('hiddenFilterResult')" :data-p-hidden-accessible="true" >
2022-09-14 11:26:01 +00:00
{ { filterResultMessageText } }
2022-09-06 12:03:37 +00:00
< / span >
2023-05-25 14:01:28 +00:00
< button v -ripple :class ="cx('closeButton')" :aria-label ="closeAriaLabel" @click ="onCloseClick" type = "button" v-bind ="{ ...closeButtonProps, ...ptm('closeButton') }" >
< slot name = "closeicon" :class ="cx('closeIcon')" >
< component : is = "closeIcon ? 'span' : 'TimesIcon'" : class = "[cx('closeIcon'), closeIcon]" v -bind = " ptm ( ' closeIcon ' ) " / >
2023-04-04 12:41:18 +00:00
< / slot >
2022-09-06 12:03:37 +00:00
< / button >
< / div >
2023-05-25 14:01:28 +00:00
< div :class ="cx('wrapper')" : style = "{ 'max-height': virtualScrollerDisabled ? scrollHeight : '' }" v-bind ="ptm('wrapper')" >
2023-05-25 14:02:41 +00:00
< VirtualScroller :ref ="virtualScrollerRef" v-bind ="virtualScrollerOptions" :items="visibleOptions" :style="{ height: scrollHeight }" :tabindex="-1" :disabled="virtualScrollerDisabled" :pt="ptm('virtualScroller')" >
2022-09-06 12:03:37 +00:00
< template v -slot : content = "{ styleClass, contentRef, items, getItemOptions, contentStyle, itemSize }" >
2023-05-25 14:01:28 +00:00
< ul : ref = "(el) => listRef(el, contentRef)" : id = "id + '_list'" : class = "[cx('list'), styleClass]" :style ="contentStyle" role = "listbox" aria -multiselectable = " true " v-bind ="ptm('list')" >
2022-09-06 12:03:37 +00:00
< template v-for ="(option, i) of items" :key="getOptionRenderKey(option, getOptionIndex(i, getItemOptions))" >
2023-05-25 14:01:28 +00:00
< li v-if ="isOptionGroup(option)" :id="id + '_' + getOptionIndex(i, getItemOptions)" :style="{ height: itemSize ? itemSize + 'px' : undefined }" :class="cx('itemGroup')" role="option" v-bind="ptm('itemGroup')" >
2022-09-14 11:26:01 +00:00
< slot name = "optiongroup" :option ="option.optionGroup" : index = "getOptionIndex(i, getItemOptions)" > { { getOptionGroupLabel ( option . optionGroup ) } } < / slot >
2022-09-06 12:03:37 +00:00
< / li >
2022-09-14 11:26:01 +00:00
< li
v - else
: id = "id + '_' + getOptionIndex(i, getItemOptions)"
v - ripple
: style = "{ height: itemSize ? itemSize + 'px' : undefined }"
2023-05-25 14:01:28 +00:00
: class = "cx('item', { option, index: i, getItemOptions })"
2022-09-14 11:26:01 +00:00
role = "option"
: aria - label = "getOptionLabel(option)"
: aria - selected = "isSelected(option)"
: aria - disabled = "isOptionDisabled(option)"
: aria - setsize = "ariaSetSize"
: aria - posinset = "getAriaPosInset(getOptionIndex(i, getItemOptions))"
@ click = "onOptionSelect($event, option, getOptionIndex(i, getItemOptions), true)"
@ mousemove = "onOptionMouseMove($event, getOptionIndex(i, getItemOptions))"
2023-05-08 07:00:16 +00:00
v - bind = "getCheckboxPTOptions(option, getItemOptions, i, 'item')"
2023-05-25 14:01:28 +00:00
: data - p - highlight = "isSelected(option)"
: data - p - focused = "focusedOptionIndex === getOptionIndex(i, getItemOptions)"
: data - p - disabled = "isOptionDisabled(option)"
2022-09-14 11:26:01 +00:00
>
2024-02-02 11:46:26 +00:00
< Checkbox :modelValue ="isSelected(option)" :binary ="true" :tabindex ="-1" :variant ="variant" :unstyled ="unstyled" : pt = "getCheckboxPTOptions(option, getItemOptions, i, 'itemCheckbox')" >
2024-01-14 13:38:51 +00:00
< template # icon = "slotProps" >
< component v -if = " $ slots.itemcheckboxicon " :is ="$slots.itemcheckboxicon" :checked ="slotProps.checked" :class ="slotProps.class" / >
< component
v - else - if = "slotProps.checked"
: is = "checkboxIcon ? 'span' : 'CheckIcon'"
: class = "[slotProps.class, { [checkboxIcon]: slotProps.checked }]"
v - bind = "getCheckboxPTOptions(option, getItemOptions, i, 'itemCheckbox.icon')"
/ >
< / template >
< / Checkbox >
2022-09-06 12:03:37 +00:00
< slot name = "option" :option ="option" : index = "getOptionIndex(i, getItemOptions)" >
2023-05-08 07:00:16 +00:00
< span v-bind ="ptm('option')" > {{ getOptionLabel ( option ) }} < / span >
2022-09-06 12:03:37 +00:00
< / slot >
< / li >
< / template >
2023-05-25 14:01:28 +00:00
< li v-if ="filterValue && (!items || (items && items.length === 0))" :class="cx('emptyMessage')" role="option" v-bind="ptm('emptyMessage')" >
2022-09-14 11:26:01 +00:00
< slot name = "emptyfilter" > { { emptyFilterMessageText } } < / slot >
2022-09-06 12:03:37 +00:00
< / li >
2023-05-25 14:01:28 +00:00
< li v -else -if = " ! options | | ( options & & options.length = = = 0 ) " :class ="cx('emptyMessage')" role = "option" v-bind ="ptm('emptyMessage')" >
2022-09-14 11:26:01 +00:00
< slot name = "empty" > { { emptyMessageText } } < / slot >
2022-09-06 12:03:37 +00:00
< / li >
< / ul >
< / template >
< template v-if ="$slots.loader" v-slot:loader="{ options }" >
< slot name = "loader" :options ="options" > < / slot >
< / template >
< / VirtualScroller >
< / div >
< slot name = "footer" :value ="modelValue" :options ="visibleOptions" > < / slot >
2023-07-03 22:20:35 +00:00
< span v-if ="!options || (options && options.length === 0)" role="status" aria-live="polite" class="p-hidden-accessible" v-bind="ptm('hiddenEmptyMessage')" :data-p-hidden-accessible="true" >
2022-12-08 11:04:25 +00:00
{ { emptyMessageText } }
< / span >
2023-07-03 22:20:35 +00:00
< span role = "status" aria -live = " polite " class = "p-hidden-accessible" v-bind ="ptm('hiddenSelectedMessage')" :data-p-hidden-accessible="true" >
2022-12-08 11:04:25 +00:00
{ { selectedMessageText } }
< / span >
2023-05-25 14:01:28 +00:00
< span
ref = "lastHiddenFocusableElementOnOverlay"
role = "presentation"
aria - hidden = "true"
2023-07-03 22:20:35 +00:00
class = "p-hidden-accessible p-hidden-focusable"
2023-05-25 14:01:28 +00:00
: tabindex = "0"
@ focus = "onLastHiddenFocus"
v - bind = "ptm('hiddenLastFocusableEl')"
: data - p - hidden - accessible = "true"
: data - p - hidden - focusable = "true"
> < / span >
2022-09-06 12:03:37 +00:00
< / div >
< / transition >
< / Portal >
< / div >
< / template >
< script >
2022-09-14 11:26:01 +00:00
import { FilterService } from 'primevue/api' ;
2024-01-14 13:38:51 +00:00
import Checkbox from 'primevue/checkbox' ;
2023-04-18 12:53:43 +00:00
import CheckIcon from 'primevue/icons/check' ;
import ChevronDownIcon from 'primevue/icons/chevrondown' ;
import SearchIcon from 'primevue/icons/search' ;
import SpinnerIcon from 'primevue/icons/spinner' ;
import TimesIcon from 'primevue/icons/times' ;
import TimesCircleIcon from 'primevue/icons/timescircle' ;
2022-12-08 11:04:25 +00:00
import OverlayEventBus from 'primevue/overlayeventbus' ;
import Portal from 'primevue/portal' ;
2022-09-06 12:03:37 +00:00
import Ripple from 'primevue/ripple' ;
2022-12-08 11:04:25 +00:00
import { ConnectedOverlayScrollHandler , DomHandler , ObjectUtils , UniqueComponentId , ZIndexUtils } from 'primevue/utils' ;
2022-09-06 12:03:37 +00:00
import VirtualScroller from 'primevue/virtualscroller' ;
2023-05-25 14:01:28 +00:00
import BaseMultiSelect from './BaseMultiSelect.vue' ;
2022-09-06 12:03:37 +00:00
export default {
name : 'MultiSelect' ,
2023-05-25 14:01:28 +00:00
extends : BaseMultiSelect ,
2024-02-11 23:48:09 +00:00
inheritAttrs : false ,
2022-09-14 11:26:01 +00:00
emits : [ 'update:modelValue' , 'change' , 'focus' , 'blur' , 'before-show' , 'before-hide' , 'show' , 'hide' , 'filter' , 'selectall-change' ] ,
2022-09-06 12:03:37 +00:00
outsideClickListener : null ,
scrollHandler : null ,
resizeListener : null ,
overlay : null ,
list : null ,
virtualScroller : null ,
startRangeIndex : - 1 ,
searchTimeout : null ,
searchValue : '' ,
selectOnFocus : false ,
data ( ) {
return {
2023-01-19 04:01:03 +00:00
id : this . $attrs . id ,
2024-01-23 13:08:06 +00:00
clicked : false ,
2022-09-06 12:03:37 +00:00
focused : false ,
focusedOptionIndex : - 1 ,
filterValue : null ,
overlayVisible : false
2022-09-14 11:26:01 +00:00
} ;
2022-09-06 12:03:37 +00:00
} ,
watch : {
2023-01-19 04:01:03 +00:00
'$attrs.id' : function ( newValue ) {
this . id = newValue || UniqueComponentId ( ) ;
} ,
2022-09-06 12:03:37 +00:00
options ( ) {
this . autoUpdateModel ( ) ;
}
} ,
2024-01-16 11:28:03 +00:00
mounted ( ) {
2024-01-23 18:06:31 +00:00
this . id = this . id || UniqueComponentId ( ) ;
2022-09-06 12:03:37 +00:00
this . autoUpdateModel ( ) ;
} ,
beforeUnmount ( ) {
this . unbindOutsideClickListener ( ) ;
this . unbindResizeListener ( ) ;
if ( this . scrollHandler ) {
this . scrollHandler . destroy ( ) ;
this . scrollHandler = null ;
}
if ( this . overlay ) {
ZIndexUtils . clear ( this . overlay ) ;
this . overlay = null ;
}
} ,
methods : {
getOptionIndex ( index , fn ) {
2022-09-14 11:26:01 +00:00
return this . virtualScrollerDisabled ? index : fn && fn ( index ) [ 'index' ] ;
2022-09-06 12:03:37 +00:00
} ,
getOptionLabel ( option ) {
return this . optionLabel ? ObjectUtils . resolveFieldData ( option , this . optionLabel ) : option ;
} ,
getOptionValue ( option ) {
return this . optionValue ? ObjectUtils . resolveFieldData ( option , this . optionValue ) : option ;
} ,
getOptionRenderKey ( option ) {
return this . dataKey ? ObjectUtils . resolveFieldData ( option , this . dataKey ) : this . getOptionLabel ( option ) ;
} ,
2023-05-08 07:00:16 +00:00
getHeaderCheckboxPTOptions ( key ) {
return this . ptm ( key , {
context : {
2024-01-14 13:38:51 +00:00
selected : this . allSelected
2023-05-08 07:00:16 +00:00
}
} ) ;
} ,
getCheckboxPTOptions ( option , itemOptions , index , key ) {
return this . ptm ( key , {
context : {
selected : this . isSelected ( option ) ,
focused : this . focusedOptionIndex === this . getOptionIndex ( index , itemOptions ) ,
disabled : this . isOptionDisabled ( option )
}
} ) ;
} ,
2022-09-06 12:03:37 +00:00
isOptionDisabled ( option ) {
if ( this . maxSelectionLimitReached && ! this . isSelected ( option ) ) {
return true ;
}
return this . optionDisabled ? ObjectUtils . resolveFieldData ( option , this . optionDisabled ) : false ;
} ,
isOptionGroup ( option ) {
return this . optionGroupLabel && option . optionGroup && option . group ;
} ,
getOptionGroupLabel ( optionGroup ) {
return ObjectUtils . resolveFieldData ( optionGroup , this . optionGroupLabel ) ;
} ,
getOptionGroupChildren ( optionGroup ) {
return ObjectUtils . resolveFieldData ( optionGroup , this . optionGroupChildren ) ;
} ,
getAriaPosInset ( index ) {
2022-09-14 11:26:01 +00:00
return ( this . optionGroupLabel ? index - this . visibleOptions . slice ( 0 , index ) . filter ( ( option ) => this . isOptionGroup ( option ) ) . length : index ) + 1 ;
2022-09-06 12:03:37 +00:00
} ,
show ( isFocus ) {
this . $emit ( 'before-show' ) ;
this . overlayVisible = true ;
2024-01-23 13:08:06 +00:00
this . focusedOptionIndex = this . focusedOptionIndex !== - 1 ? this . focusedOptionIndex : this . autoOptionFocus ? this . findFirstFocusedOptionIndex ( ) : this . findSelectedOptionIndex ( ) ;
2022-09-06 12:03:37 +00:00
2022-09-14 11:26:01 +00:00
isFocus && DomHandler . focus ( this . $refs . focusInput ) ;
2022-09-06 12:03:37 +00:00
} ,
hide ( isFocus ) {
2022-12-08 11:04:25 +00:00
const _hide = ( ) => {
this . $emit ( 'before-hide' ) ;
this . overlayVisible = false ;
2024-01-23 13:08:06 +00:00
this . clicked = false ;
2022-12-08 11:04:25 +00:00
this . focusedOptionIndex = - 1 ;
this . searchValue = '' ;
2022-09-06 12:03:37 +00:00
2022-12-08 11:04:25 +00:00
this . resetFilterOnHide && ( this . filterValue = null ) ;
isFocus && DomHandler . focus ( this . $refs . focusInput ) ;
} ;
setTimeout ( ( ) => {
_hide ( ) ;
} , 0 ) ; // For ScreenReaders
2022-09-06 12:03:37 +00:00
} ,
onFocus ( event ) {
2023-01-11 08:57:49 +00:00
if ( this . disabled ) {
// For ScreenReaders
return ;
}
2022-09-06 12:03:37 +00:00
this . focused = true ;
2024-01-23 13:08:06 +00:00
if ( this . overlayVisible ) {
this . focusedOptionIndex = this . focusedOptionIndex !== - 1 ? this . focusedOptionIndex : this . autoOptionFocus ? this . findFirstFocusedOptionIndex ( ) : this . findSelectedOptionIndex ( ) ;
this . scrollInView ( this . focusedOptionIndex ) ;
}
2022-09-06 12:03:37 +00:00
this . $emit ( 'focus' , event ) ;
} ,
onBlur ( event ) {
2024-01-23 13:08:06 +00:00
this . clicked = false ;
2022-09-06 12:03:37 +00:00
this . focused = false ;
this . focusedOptionIndex = - 1 ;
this . searchValue = '' ;
this . $emit ( 'blur' , event ) ;
} ,
onKeyDown ( event ) {
2023-01-11 08:57:49 +00:00
if ( this . disabled ) {
event . preventDefault ( ) ;
return ;
}
2022-09-14 11:26:01 +00:00
const metaKey = event . metaKey || event . ctrlKey ;
2022-09-06 12:03:37 +00:00
switch ( event . code ) {
case 'ArrowDown' :
this . onArrowDownKey ( event ) ;
break ;
case 'ArrowUp' :
this . onArrowUpKey ( event ) ;
break ;
case 'Home' :
this . onHomeKey ( event ) ;
break ;
case 'End' :
this . onEndKey ( event ) ;
break ;
case 'PageDown' :
this . onPageDownKey ( event ) ;
break ;
case 'PageUp' :
this . onPageUpKey ( event ) ;
break ;
case 'Enter' :
2023-12-20 10:45:43 +00:00
case 'NumpadEnter' :
2022-09-06 12:03:37 +00:00
case 'Space' :
this . onEnterKey ( event ) ;
break ;
case 'Escape' :
this . onEscapeKey ( event ) ;
break ;
case 'Tab' :
this . onTabKey ( event ) ;
break ;
case 'ShiftLeft' :
case 'ShiftRight' :
this . onShiftKey ( event ) ;
break ;
default :
2022-09-14 11:26:01 +00:00
if ( event . code === 'KeyA' && metaKey ) {
const value = this . visibleOptions . filter ( ( option ) => this . isValidOption ( option ) ) . map ( ( option ) => this . getOptionValue ( option ) ) ;
2022-09-06 12:03:37 +00:00
this . updateModel ( event , value ) ;
event . preventDefault ( ) ;
break ;
}
2022-09-14 11:26:01 +00:00
if ( ! metaKey && ObjectUtils . isPrintableCharacter ( event . key ) ) {
2022-09-06 12:03:37 +00:00
! this . overlayVisible && this . show ( ) ;
this . searchOptions ( event ) ;
event . preventDefault ( ) ;
}
break ;
}
2024-01-23 13:08:06 +00:00
this . clicked = false ;
2022-09-06 12:03:37 +00:00
} ,
onContainerClick ( event ) {
if ( this . disabled || this . loading ) {
return ;
}
if ( ! this . overlay || ! this . overlay . contains ( event . target ) ) {
this . overlayVisible ? this . hide ( true ) : this . show ( true ) ;
}
2024-01-23 13:08:06 +00:00
this . clicked = true ;
2022-09-06 12:03:37 +00:00
} ,
onFirstHiddenFocus ( event ) {
2023-05-25 14:01:28 +00:00
const focusableEl = event . relatedTarget === this . $refs . focusInput ? DomHandler . getFirstFocusableElement ( this . overlay , ':not([data-p-hidden-focusable="true"])' ) : this . $refs . focusInput ;
2022-09-14 11:26:01 +00:00
2022-12-08 11:04:25 +00:00
DomHandler . focus ( focusableEl ) ;
2022-09-06 12:03:37 +00:00
} ,
2022-12-08 11:04:25 +00:00
onLastHiddenFocus ( event ) {
2023-05-25 14:01:28 +00:00
const focusableEl = event . relatedTarget === this . $refs . focusInput ? DomHandler . getLastFocusableElement ( this . overlay , ':not([data-p-hidden-focusable="true"])' ) : this . $refs . focusInput ;
2022-12-08 11:04:25 +00:00
DomHandler . focus ( focusableEl ) ;
2022-09-06 12:03:37 +00:00
} ,
onCloseClick ( ) {
this . hide ( true ) ;
} ,
onOptionSelect ( event , option , index = - 1 , isFocus = false ) {
if ( this . disabled || this . isOptionDisabled ( option ) ) {
return ;
}
let selected = this . isSelected ( option ) ;
let value = null ;
2022-09-14 11:26:01 +00:00
if ( selected ) value = this . modelValue . filter ( ( val ) => ! ObjectUtils . equals ( val , this . getOptionValue ( option ) , this . equalityKey ) ) ;
else value = [ ... ( this . modelValue || [ ] ) , this . getOptionValue ( option ) ] ;
2022-09-06 12:03:37 +00:00
this . updateModel ( event , value ) ;
2024-01-23 13:08:06 +00:00
index !== - 1 && ( this . focusedOptionIndex = index ) ;
2022-12-08 11:04:25 +00:00
isFocus && DomHandler . focus ( this . $refs . focusInput ) ;
2022-09-06 12:03:37 +00:00
} ,
onOptionMouseMove ( event , index ) {
if ( this . focusOnHover ) {
this . changeFocusedOptionIndex ( event , index ) ;
}
} ,
onOptionSelectRange ( event , start = - 1 , end = - 1 ) {
start === - 1 && ( start = this . findNearestSelectedOptionIndex ( end , true ) ) ;
end === - 1 && ( end = this . findNearestSelectedOptionIndex ( start ) ) ;
if ( start !== - 1 && end !== - 1 ) {
const rangeStart = Math . min ( start , end ) ;
const rangeEnd = Math . max ( start , end ) ;
2022-09-14 11:26:01 +00:00
const value = this . visibleOptions
. slice ( rangeStart , rangeEnd + 1 )
. filter ( ( option ) => this . isValidOption ( option ) )
. map ( ( option ) => this . getOptionValue ( option ) ) ;
2022-09-06 12:03:37 +00:00
this . updateModel ( event , value ) ;
}
} ,
onFilterChange ( event ) {
const value = event . target . value ;
this . filterValue = value ;
this . focusedOptionIndex = - 1 ;
this . $emit ( 'filter' , { originalEvent : event , value } ) ;
! this . virtualScrollerDisabled && this . virtualScroller . scrollToIndex ( 0 ) ;
} ,
onFilterKeyDown ( event ) {
switch ( event . code ) {
case 'ArrowDown' :
this . onArrowDownKey ( event ) ;
break ;
case 'ArrowUp' :
this . onArrowUpKey ( event , true ) ;
break ;
case 'ArrowLeft' :
case 'ArrowRight' :
this . onArrowLeftKey ( event , true ) ;
break ;
case 'Home' :
this . onHomeKey ( event , true ) ;
break ;
case 'End' :
this . onEndKey ( event , true ) ;
break ;
case 'Enter' :
2023-12-20 10:45:43 +00:00
case 'NumpadEnter' :
2022-09-06 12:03:37 +00:00
this . onEnterKey ( event ) ;
break ;
case 'Escape' :
this . onEscapeKey ( event ) ;
break ;
case 'Tab' :
this . onTabKey ( event , true ) ;
break ;
default :
break ;
}
} ,
onFilterBlur ( ) {
this . focusedOptionIndex = - 1 ;
} ,
onFilterUpdated ( ) {
if ( this . overlayVisible ) {
this . alignOverlay ( ) ;
}
} ,
onOverlayClick ( event ) {
OverlayEventBus . emit ( 'overlay-click' , {
originalEvent : event ,
target : this . $el
} ) ;
} ,
onOverlayKeyDown ( event ) {
switch ( event . code ) {
case 'Escape' :
this . onEscapeKey ( event ) ;
break ;
default :
break ;
}
} ,
onArrowDownKey ( event ) {
2024-01-23 13:08:06 +00:00
if ( ! this . overlayVisible ) {
this . show ( ) ;
} else {
const optionIndex = this . focusedOptionIndex !== - 1 ? this . findNextOptionIndex ( this . focusedOptionIndex ) : this . clicked ? this . findFirstOptionIndex ( ) : this . findFirstFocusedOptionIndex ( ) ;
2022-09-06 12:03:37 +00:00
2024-01-23 13:08:06 +00:00
if ( event . shiftKey ) {
this . onOptionSelectRange ( event , this . startRangeIndex , optionIndex ) ;
}
2022-09-06 12:03:37 +00:00
2024-01-23 13:08:06 +00:00
this . changeFocusedOptionIndex ( event , optionIndex ) ;
}
2022-09-06 12:03:37 +00:00
event . preventDefault ( ) ;
} ,
onArrowUpKey ( event , pressedInInputText = false ) {
if ( event . altKey && ! pressedInInputText ) {
if ( this . focusedOptionIndex !== - 1 ) {
this . onOptionSelect ( event , this . visibleOptions [ this . focusedOptionIndex ] ) ;
}
this . overlayVisible && this . hide ( ) ;
event . preventDefault ( ) ;
2022-09-14 11:26:01 +00:00
} else {
2024-01-23 13:08:06 +00:00
const optionIndex = this . focusedOptionIndex !== - 1 ? this . findPrevOptionIndex ( this . focusedOptionIndex ) : this . clicked ? this . findLastOptionIndex ( ) : this . findLastFocusedOptionIndex ( ) ;
2022-09-06 12:03:37 +00:00
if ( event . shiftKey ) {
this . onOptionSelectRange ( event , optionIndex , this . startRangeIndex ) ;
}
this . changeFocusedOptionIndex ( event , optionIndex ) ;
! this . overlayVisible && this . show ( ) ;
event . preventDefault ( ) ;
}
} ,
onArrowLeftKey ( event , pressedInInputText = false ) {
pressedInInputText && ( this . focusedOptionIndex = - 1 ) ;
} ,
onHomeKey ( event , pressedInInputText = false ) {
2023-01-09 13:06:17 +00:00
const { currentTarget } = event ;
2022-09-06 12:03:37 +00:00
if ( pressedInInputText ) {
2023-01-09 13:06:17 +00:00
const len = currentTarget . value . length ;
currentTarget . setSelectionRange ( 0 , event . shiftKey ? len : 0 ) ;
2022-09-06 12:03:37 +00:00
this . focusedOptionIndex = - 1 ;
2022-09-14 11:26:01 +00:00
} else {
2022-09-06 12:03:37 +00:00
let metaKey = event . metaKey || event . ctrlKey ;
let optionIndex = this . findFirstOptionIndex ( ) ;
if ( event . shiftKey && metaKey ) {
this . onOptionSelectRange ( event , optionIndex , this . startRangeIndex ) ;
}
this . changeFocusedOptionIndex ( event , optionIndex ) ;
! this . overlayVisible && this . show ( ) ;
}
event . preventDefault ( ) ;
} ,
onEndKey ( event , pressedInInputText = false ) {
2023-01-09 13:06:17 +00:00
const { currentTarget } = event ;
2022-09-06 12:03:37 +00:00
if ( pressedInInputText ) {
2023-01-09 13:06:17 +00:00
const len = currentTarget . value . length ;
2022-09-14 11:26:01 +00:00
2023-01-09 13:06:17 +00:00
currentTarget . setSelectionRange ( event . shiftKey ? 0 : len , len ) ;
2022-09-06 12:03:37 +00:00
this . focusedOptionIndex = - 1 ;
2022-09-14 11:26:01 +00:00
} else {
2022-09-06 12:03:37 +00:00
let metaKey = event . metaKey || event . ctrlKey ;
let optionIndex = this . findLastOptionIndex ( ) ;
if ( event . shiftKey && metaKey ) {
this . onOptionSelectRange ( event , this . startRangeIndex , optionIndex ) ;
}
this . changeFocusedOptionIndex ( event , optionIndex ) ;
! this . overlayVisible && this . show ( ) ;
}
event . preventDefault ( ) ;
} ,
onPageUpKey ( event ) {
this . scrollInView ( 0 ) ;
event . preventDefault ( ) ;
} ,
onPageDownKey ( event ) {
this . scrollInView ( this . visibleOptions . length - 1 ) ;
event . preventDefault ( ) ;
} ,
onEnterKey ( event ) {
if ( ! this . overlayVisible ) {
2024-01-23 13:08:06 +00:00
this . focusedOptionIndex = - 1 ; // reset
2022-09-06 12:03:37 +00:00
this . onArrowDownKey ( event ) ;
2022-09-14 11:26:01 +00:00
} else {
2022-09-06 12:03:37 +00:00
if ( this . focusedOptionIndex !== - 1 ) {
2022-09-14 11:26:01 +00:00
if ( event . shiftKey ) this . onOptionSelectRange ( event , this . focusedOptionIndex ) ;
else this . onOptionSelect ( event , this . visibleOptions [ this . focusedOptionIndex ] ) ;
2022-09-06 12:03:37 +00:00
}
}
event . preventDefault ( ) ;
} ,
onEscapeKey ( event ) {
this . overlayVisible && this . hide ( true ) ;
event . preventDefault ( ) ;
} ,
onTabKey ( event , pressedInInputText = false ) {
if ( ! pressedInInputText ) {
if ( this . overlayVisible && this . hasFocusableElements ( ) ) {
2022-12-08 11:04:25 +00:00
DomHandler . focus ( event . shiftKey ? this . $refs . lastHiddenFocusableElementOnOverlay : this . $refs . firstHiddenFocusableElementOnOverlay ) ;
2022-09-06 12:03:37 +00:00
event . preventDefault ( ) ;
2022-09-14 11:26:01 +00:00
} else {
2022-09-06 12:03:37 +00:00
if ( this . focusedOptionIndex !== - 1 ) {
this . onOptionSelect ( event , this . visibleOptions [ this . focusedOptionIndex ] ) ;
}
this . overlayVisible && this . hide ( this . filter ) ;
}
}
} ,
onShiftKey ( ) {
this . startRangeIndex = this . focusedOptionIndex ;
} ,
onOverlayEnter ( el ) {
ZIndexUtils . set ( 'overlay' , el , this . $primevue . config . zIndex . overlay ) ;
2023-05-25 14:01:28 +00:00
DomHandler . addStyles ( el , { position : 'absolute' , top : '0' , left : '0' } ) ;
2022-09-06 12:03:37 +00:00
this . alignOverlay ( ) ;
this . scrollInView ( ) ;
2022-09-14 11:26:01 +00:00
this . autoFilterFocus && DomHandler . focus ( this . $refs . filterInput ) ;
2022-09-06 12:03:37 +00:00
} ,
onOverlayAfterEnter ( ) {
this . bindOutsideClickListener ( ) ;
this . bindScrollListener ( ) ;
this . bindResizeListener ( ) ;
this . $emit ( 'show' ) ;
} ,
onOverlayLeave ( ) {
this . unbindOutsideClickListener ( ) ;
this . unbindScrollListener ( ) ;
this . unbindResizeListener ( ) ;
this . $emit ( 'hide' ) ;
this . overlay = null ;
} ,
onOverlayAfterLeave ( el ) {
ZIndexUtils . clear ( el ) ;
} ,
alignOverlay ( ) {
if ( this . appendTo === 'self' ) {
DomHandler . relativePosition ( this . overlay , this . $el ) ;
2022-09-14 11:26:01 +00:00
} else {
2022-09-06 12:03:37 +00:00
this . overlay . style . minWidth = DomHandler . getOuterWidth ( this . $el ) + 'px' ;
DomHandler . absolutePosition ( this . overlay , this . $el ) ;
}
} ,
bindOutsideClickListener ( ) {
if ( ! this . outsideClickListener ) {
this . outsideClickListener = ( event ) => {
if ( this . overlayVisible && this . isOutsideClicked ( event ) ) {
this . hide ( ) ;
}
} ;
2022-09-14 11:26:01 +00:00
2022-09-06 12:03:37 +00:00
document . addEventListener ( 'click' , this . outsideClickListener ) ;
}
} ,
unbindOutsideClickListener ( ) {
if ( this . outsideClickListener ) {
document . removeEventListener ( 'click' , this . outsideClickListener ) ;
this . outsideClickListener = null ;
}
} ,
bindScrollListener ( ) {
if ( ! this . scrollHandler ) {
this . scrollHandler = new ConnectedOverlayScrollHandler ( this . $refs . container , ( ) => {
if ( this . overlayVisible ) {
this . hide ( ) ;
}
} ) ;
}
this . scrollHandler . bindScrollListener ( ) ;
} ,
unbindScrollListener ( ) {
if ( this . scrollHandler ) {
this . scrollHandler . unbindScrollListener ( ) ;
}
} ,
bindResizeListener ( ) {
if ( ! this . resizeListener ) {
this . resizeListener = ( ) => {
if ( this . overlayVisible && ! DomHandler . isTouchDevice ( ) ) {
this . hide ( ) ;
}
} ;
2022-09-14 11:26:01 +00:00
2022-09-06 12:03:37 +00:00
window . addEventListener ( 'resize' , this . resizeListener ) ;
}
} ,
unbindResizeListener ( ) {
if ( this . resizeListener ) {
window . removeEventListener ( 'resize' , this . resizeListener ) ;
this . resizeListener = null ;
}
} ,
isOutsideClicked ( event ) {
return ! ( this . $el . isSameNode ( event . target ) || this . $el . contains ( event . target ) || ( this . overlay && this . overlay . contains ( event . target ) ) ) ;
} ,
getLabelByValue ( value ) {
2022-09-14 11:26:01 +00:00
const options = this . optionGroupLabel ? this . flatOptions ( this . options ) : this . options || [ ] ;
const matchedOption = options . find ( ( option ) => ! this . isOptionGroup ( option ) && ObjectUtils . equals ( this . getOptionValue ( option ) , value , this . equalityKey ) ) ;
return matchedOption ? this . getOptionLabel ( matchedOption ) : null ;
2022-09-06 12:03:37 +00:00
} ,
getSelectedItemsLabel ( ) {
let pattern = /{(.*?)}/ ;
2023-11-02 12:53:39 +00:00
const selectedItemsLabel = this . selectedItemsLabel || this . $primevue . config . locale . selectionMessage ;
2023-10-17 09:16:32 +00:00
2023-10-10 21:48:17 +00:00
if ( pattern . test ( selectedItemsLabel ) ) {
return selectedItemsLabel . replace ( selectedItemsLabel . match ( pattern ) [ 0 ] , this . modelValue . length + '' ) ;
2022-09-06 12:03:37 +00:00
}
2023-10-17 09:16:32 +00:00
2023-10-10 21:48:17 +00:00
return selectedItemsLabel ;
2022-09-06 12:03:37 +00:00
} ,
onToggleAll ( event ) {
if ( this . selectAll !== null ) {
2022-09-14 11:26:01 +00:00
this . $emit ( 'selectall-change' , { originalEvent : event , checked : ! this . allSelected } ) ;
} else {
2022-12-08 11:04:25 +00:00
const value = this . allSelected ? [ ] : this . visibleOptions . filter ( ( option ) => this . isValidOption ( option ) ) . map ( ( option ) => this . getOptionValue ( option ) ) ;
2022-09-06 12:03:37 +00:00
this . updateModel ( event , value ) ;
}
} ,
removeOption ( event , optionValue ) {
2022-09-14 11:26:01 +00:00
let value = this . modelValue . filter ( ( val ) => ! ObjectUtils . equals ( val , optionValue , this . equalityKey ) ) ;
2022-09-06 12:03:37 +00:00
this . updateModel ( event , value ) ;
} ,
clearFilter ( ) {
this . filterValue = null ;
} ,
hasFocusableElements ( ) {
2023-05-25 14:01:28 +00:00
return DomHandler . getFocusableElements ( this . overlay , ':not([data-p-hidden-focusable="true"])' ) . length > 0 ;
2022-09-06 12:03:37 +00:00
} ,
isOptionMatched ( option ) {
2024-01-24 11:54:14 +00:00
return this . isValidOption ( option ) && this . getOptionLabel ( option ) ? . toLocaleLowerCase ( this . filterLocale ) . startsWith ( this . searchValue . toLocaleLowerCase ( this . filterLocale ) ) ;
2022-09-06 12:03:37 +00:00
} ,
isValidOption ( option ) {
2023-10-24 13:06:14 +00:00
return ObjectUtils . isNotEmpty ( option ) && ! ( this . isOptionDisabled ( option ) || this . isOptionGroup ( option ) ) ;
2022-09-06 12:03:37 +00:00
} ,
isValidSelectedOption ( option ) {
return this . isValidOption ( option ) && this . isSelected ( option ) ;
} ,
2024-01-23 13:08:06 +00:00
isEquals ( value1 , value2 ) {
return ObjectUtils . equals ( value1 , value2 , this . equalityKey ) ;
} ,
2022-09-06 12:03:37 +00:00
isSelected ( option ) {
const optionValue = this . getOptionValue ( option ) ;
2022-09-14 11:26:01 +00:00
2024-01-23 13:08:06 +00:00
return ( this . modelValue || [ ] ) . some ( ( value ) => this . isEquals ( value , optionValue ) ) ;
2022-09-06 12:03:37 +00:00
} ,
findFirstOptionIndex ( ) {
2022-09-14 11:26:01 +00:00
return this . visibleOptions . findIndex ( ( option ) => this . isValidOption ( option ) ) ;
2022-09-06 12:03:37 +00:00
} ,
findLastOptionIndex ( ) {
2022-09-14 11:26:01 +00:00
return ObjectUtils . findLastIndex ( this . visibleOptions , ( option ) => this . isValidOption ( option ) ) ;
2022-09-06 12:03:37 +00:00
} ,
findNextOptionIndex ( index ) {
2022-09-14 11:26:01 +00:00
const matchedOptionIndex = index < this . visibleOptions . length - 1 ? this . visibleOptions . slice ( index + 1 ) . findIndex ( ( option ) => this . isValidOption ( option ) ) : - 1 ;
2022-09-06 12:03:37 +00:00
return matchedOptionIndex > - 1 ? matchedOptionIndex + index + 1 : index ;
} ,
findPrevOptionIndex ( index ) {
2022-09-14 11:26:01 +00:00
const matchedOptionIndex = index > 0 ? ObjectUtils . findLastIndex ( this . visibleOptions . slice ( 0 , index ) , ( option ) => this . isValidOption ( option ) ) : - 1 ;
2022-09-06 12:03:37 +00:00
return matchedOptionIndex > - 1 ? matchedOptionIndex : index ;
} ,
2024-01-23 13:08:06 +00:00
findSelectedOptionIndex ( ) {
if ( this . hasSelectedOption ) {
for ( let index = this . modelValue . length - 1 ; index >= 0 ; index -- ) {
const value = this . modelValue [ index ] ;
const matchedOptionIndex = this . visibleOptions . findIndex ( ( option ) => this . isValidSelectedOption ( option ) && this . isEquals ( value , this . getOptionValue ( option ) ) ) ;
if ( matchedOptionIndex > - 1 ) return matchedOptionIndex ;
}
}
return - 1 ;
} ,
2022-09-06 12:03:37 +00:00
findFirstSelectedOptionIndex ( ) {
2022-09-14 11:26:01 +00:00
return this . hasSelectedOption ? this . visibleOptions . findIndex ( ( option ) => this . isValidSelectedOption ( option ) ) : - 1 ;
2022-09-06 12:03:37 +00:00
} ,
findLastSelectedOptionIndex ( ) {
2022-09-14 11:26:01 +00:00
return this . hasSelectedOption ? ObjectUtils . findLastIndex ( this . visibleOptions , ( option ) => this . isValidSelectedOption ( option ) ) : - 1 ;
2022-09-06 12:03:37 +00:00
} ,
findNextSelectedOptionIndex ( index ) {
2022-09-14 11:26:01 +00:00
const matchedOptionIndex = this . hasSelectedOption && index < this . visibleOptions . length - 1 ? this . visibleOptions . slice ( index + 1 ) . findIndex ( ( option ) => this . isValidSelectedOption ( option ) ) : - 1 ;
2022-09-06 12:03:37 +00:00
return matchedOptionIndex > - 1 ? matchedOptionIndex + index + 1 : - 1 ;
} ,
findPrevSelectedOptionIndex ( index ) {
2022-09-14 11:26:01 +00:00
const matchedOptionIndex = this . hasSelectedOption && index > 0 ? ObjectUtils . findLastIndex ( this . visibleOptions . slice ( 0 , index ) , ( option ) => this . isValidSelectedOption ( option ) ) : - 1 ;
2022-09-06 12:03:37 +00:00
return matchedOptionIndex > - 1 ? matchedOptionIndex : - 1 ;
} ,
findNearestSelectedOptionIndex ( index , firstCheckUp = false ) {
let matchedOptionIndex = - 1 ;
if ( this . hasSelectedOption ) {
if ( firstCheckUp ) {
matchedOptionIndex = this . findPrevSelectedOptionIndex ( index ) ;
matchedOptionIndex = matchedOptionIndex === - 1 ? this . findNextSelectedOptionIndex ( index ) : matchedOptionIndex ;
2022-09-14 11:26:01 +00:00
} else {
2022-09-06 12:03:37 +00:00
matchedOptionIndex = this . findNextSelectedOptionIndex ( index ) ;
matchedOptionIndex = matchedOptionIndex === - 1 ? this . findPrevSelectedOptionIndex ( index ) : matchedOptionIndex ;
}
}
return matchedOptionIndex > - 1 ? matchedOptionIndex : index ;
} ,
findFirstFocusedOptionIndex ( ) {
2024-01-23 13:08:06 +00:00
const selectedIndex = this . findSelectedOptionIndex ( ) ;
2022-09-14 11:26:01 +00:00
2022-09-06 12:03:37 +00:00
return selectedIndex < 0 ? this . findFirstOptionIndex ( ) : selectedIndex ;
} ,
findLastFocusedOptionIndex ( ) {
2024-01-23 13:08:06 +00:00
const selectedIndex = this . findSelectedOptionIndex ( ) ;
2022-09-14 11:26:01 +00:00
2022-09-06 12:03:37 +00:00
return selectedIndex < 0 ? this . findLastOptionIndex ( ) : selectedIndex ;
} ,
searchOptions ( event ) {
this . searchValue = ( this . searchValue || '' ) + event . key ;
let optionIndex = - 1 ;
2022-09-14 11:26:01 +00:00
2024-01-23 13:08:06 +00:00
if ( ObjectUtils . isNotEmpty ( this . searchValue ) ) {
if ( this . focusedOptionIndex !== - 1 ) {
optionIndex = this . visibleOptions . slice ( this . focusedOptionIndex ) . findIndex ( ( option ) => this . isOptionMatched ( option ) ) ;
optionIndex = optionIndex === - 1 ? this . visibleOptions . slice ( 0 , this . focusedOptionIndex ) . findIndex ( ( option ) => this . isOptionMatched ( option ) ) : optionIndex + this . focusedOptionIndex ;
} else {
optionIndex = this . visibleOptions . findIndex ( ( option ) => this . isOptionMatched ( option ) ) ;
}
2022-09-06 12:03:37 +00:00
2024-01-23 13:08:06 +00:00
if ( optionIndex === - 1 && this . focusedOptionIndex === - 1 ) {
optionIndex = this . findFirstFocusedOptionIndex ( ) ;
}
2022-09-06 12:03:37 +00:00
2024-01-23 13:08:06 +00:00
if ( optionIndex !== - 1 ) {
this . changeFocusedOptionIndex ( event , optionIndex ) ;
}
2022-09-06 12:03:37 +00:00
}
if ( this . searchTimeout ) {
clearTimeout ( this . searchTimeout ) ;
}
this . searchTimeout = setTimeout ( ( ) => {
this . searchValue = '' ;
this . searchTimeout = null ;
} , 500 ) ;
} ,
changeFocusedOptionIndex ( event , index ) {
if ( this . focusedOptionIndex !== index ) {
this . focusedOptionIndex = index ;
this . scrollInView ( ) ;
2024-01-23 13:08:06 +00:00
if ( this . selectOnFocus ) {
this . onOptionSelect ( event , this . visibleOptions [ index ] ) ;
}
2022-09-06 12:03:37 +00:00
}
} ,
scrollInView ( index = - 1 ) {
2024-01-23 13:08:06 +00:00
this . $nextTick ( ( ) => {
const id = index !== - 1 ? ` ${ this . id } _ ${ index } ` : this . focusedOptionId ;
const element = DomHandler . findSingle ( this . list , ` li[id=" ${ id } "] ` ) ;
if ( element ) {
element . scrollIntoView && element . scrollIntoView ( { block : 'nearest' , inline : 'nearest' } ) ;
} else if ( ! this . virtualScrollerDisabled ) {
this . virtualScroller && this . virtualScroller . scrollToIndex ( index !== - 1 ? index : this . focusedOptionIndex ) ;
}
} ) ;
2022-09-06 12:03:37 +00:00
} ,
autoUpdateModel ( ) {
if ( this . selectOnFocus && this . autoOptionFocus && ! this . hasSelectedOption ) {
this . focusedOptionIndex = this . findFirstFocusedOptionIndex ( ) ;
const value = this . getOptionValue ( this . visibleOptions [ this . focusedOptionIndex ] ) ;
2022-09-14 11:26:01 +00:00
2022-09-06 12:03:37 +00:00
this . updateModel ( null , [ value ] ) ;
}
} ,
updateModel ( event , value ) {
this . $emit ( 'update:modelValue' , value ) ;
this . $emit ( 'change' , { originalEvent : event , value } ) ;
} ,
flatOptions ( options ) {
return ( options || [ ] ) . reduce ( ( result , option , index ) => {
result . push ( { optionGroup : option , group : true , index } ) ;
const optionGroupChildren = this . getOptionGroupChildren ( option ) ;
2022-09-14 11:26:01 +00:00
optionGroupChildren && optionGroupChildren . forEach ( ( o ) => result . push ( o ) ) ;
2022-09-06 12:03:37 +00:00
return result ;
} , [ ] ) ;
} ,
overlayRef ( el ) {
this . overlay = el ;
} ,
listRef ( el , contentRef ) {
this . list = el ;
contentRef && contentRef ( el ) ; // For VirtualScroller
} ,
virtualScrollerRef ( el ) {
this . virtualScroller = el ;
}
} ,
computed : {
visibleOptions ( ) {
2022-09-14 11:26:01 +00:00
const options = this . optionGroupLabel ? this . flatOptions ( this . options ) : this . options || [ ] ;
2022-09-06 12:03:37 +00:00
2022-12-08 11:04:25 +00:00
if ( this . filterValue ) {
const filteredOptions = FilterService . filter ( options , this . searchFields , this . filterValue , this . filterMatchMode , this . filterLocale ) ;
if ( this . optionGroupLabel ) {
const optionGroups = this . options || [ ] ;
const filtered = [ ] ;
optionGroups . forEach ( ( group ) => {
2023-03-27 10:32:57 +00:00
const groupChildren = this . getOptionGroupChildren ( group ) ;
const filteredItems = groupChildren . filter ( ( item ) => filteredOptions . includes ( item ) ) ;
2022-12-08 11:04:25 +00:00
2023-03-27 10:32:57 +00:00
if ( filteredItems . length > 0 ) filtered . push ( { ... group , [ typeof this . optionGroupChildren === 'string' ? this . optionGroupChildren : 'items' ] : [ ... filteredItems ] } ) ;
2022-12-08 11:04:25 +00:00
} ) ;
return this . flatOptions ( filtered ) ;
}
return filteredOptions ;
}
return options ;
2022-09-06 12:03:37 +00:00
} ,
2022-09-14 11:26:01 +00:00
label ( ) {
// TODO: Refactor
2022-09-06 12:03:37 +00:00
let label ;
if ( this . modelValue && this . modelValue . length ) {
if ( ObjectUtils . isNotEmpty ( this . maxSelectedLabels ) && this . modelValue . length > this . maxSelectedLabels ) {
return this . getSelectedItemsLabel ( ) ;
2022-09-14 11:26:01 +00:00
} else {
2022-09-06 12:03:37 +00:00
label = '' ;
2022-09-14 11:26:01 +00:00
2022-09-06 12:03:37 +00:00
for ( let i = 0 ; i < this . modelValue . length ; i ++ ) {
if ( i !== 0 ) {
label += ', ' ;
}
label += this . getLabelByValue ( this . modelValue [ i ] ) ;
}
}
2022-09-14 11:26:01 +00:00
} else {
2022-09-06 12:03:37 +00:00
label = this . placeholder ;
}
return label ;
} ,
2022-12-08 11:04:25 +00:00
chipSelectedItems ( ) {
return ObjectUtils . isNotEmpty ( this . maxSelectedLabels ) && this . modelValue && this . modelValue . length > this . maxSelectedLabels ? this . modelValue . slice ( 0 , this . maxSelectedLabels ) : this . modelValue ;
} ,
2022-09-06 12:03:37 +00:00
allSelected ( ) {
2022-12-08 11:04:25 +00:00
return this . selectAll !== null ? this . selectAll : ObjectUtils . isNotEmpty ( this . visibleOptions ) && this . visibleOptions . every ( ( option ) => this . isOptionGroup ( option ) || this . isOptionDisabled ( option ) || this . isSelected ( option ) ) ;
2022-09-06 12:03:37 +00:00
} ,
hasSelectedOption ( ) {
return ObjectUtils . isNotEmpty ( this . modelValue ) ;
} ,
equalityKey ( ) {
return this . optionValue ? null : this . dataKey ;
} ,
searchFields ( ) {
return this . filterFields || [ this . optionLabel ] ;
} ,
maxSelectionLimitReached ( ) {
2022-09-14 11:26:01 +00:00
return this . selectionLimit && this . modelValue && this . modelValue . length === this . selectionLimit ;
2022-09-06 12:03:37 +00:00
} ,
filterResultMessageText ( ) {
return ObjectUtils . isNotEmpty ( this . visibleOptions ) ? this . filterMessageText . replaceAll ( '{0}' , this . visibleOptions . length ) : this . emptyFilterMessageText ;
} ,
filterMessageText ( ) {
return this . filterMessage || this . $primevue . config . locale . searchMessage || '' ;
} ,
emptyFilterMessageText ( ) {
return this . emptyFilterMessage || this . $primevue . config . locale . emptySearchMessage || this . $primevue . config . locale . emptyFilterMessage || '' ;
} ,
emptyMessageText ( ) {
return this . emptyMessage || this . $primevue . config . locale . emptyMessage || '' ;
} ,
selectionMessageText ( ) {
return this . selectionMessage || this . $primevue . config . locale . selectionMessage || '' ;
} ,
emptySelectionMessageText ( ) {
return this . emptySelectionMessage || this . $primevue . config . locale . emptySelectionMessage || '' ;
} ,
selectedMessageText ( ) {
return this . hasSelectedOption ? this . selectionMessageText . replaceAll ( '{0}' , this . modelValue . length ) : this . emptySelectionMessageText ;
} ,
focusedOptionId ( ) {
return this . focusedOptionIndex !== - 1 ? ` ${ this . id } _ ${ this . focusedOptionIndex } ` : null ;
} ,
ariaSetSize ( ) {
2022-09-14 11:26:01 +00:00
return this . visibleOptions . filter ( ( option ) => ! this . isOptionGroup ( option ) ) . length ;
2022-09-06 12:03:37 +00:00
} ,
toggleAllAriaLabel ( ) {
return this . $primevue . config . locale . aria ? this . $primevue . config . locale . aria [ this . allSelected ? 'selectAll' : 'unselectAll' ] : undefined ;
} ,
closeAriaLabel ( ) {
return this . $primevue . config . locale . aria ? this . $primevue . config . locale . aria . close : undefined ;
} ,
virtualScrollerDisabled ( ) {
return ! this . virtualScrollerOptions ;
}
} ,
directives : {
2022-09-14 11:26:01 +00:00
ripple : Ripple
2022-09-06 12:03:37 +00:00
} ,
components : {
2024-01-14 13:38:51 +00:00
Checkbox ,
VirtualScroller ,
Portal ,
TimesIcon ,
SearchIcon ,
TimesCircleIcon ,
ChevronDownIcon ,
SpinnerIcon ,
CheckIcon
2022-09-06 12:03:37 +00:00
}
2022-09-14 11:26:01 +00:00
} ;
2022-09-06 12:03:37 +00:00
< / script >