Refactor #5681, #5682, #5683 - For MultiSelect

pull/5701/head
tugcekucukoglu 2024-05-06 10:05:05 +03:00
parent 06850f3ed5
commit 646ca0e239
4 changed files with 118 additions and 60 deletions

View File

@ -39,6 +39,14 @@ export default {
type: null, type: null,
default: null default: null
}, },
overlayClass: {
type: String,
default: null
},
overlayStyle: {
type: null,
default: null
},
dataKey: null, dataKey: null,
filter: Boolean, filter: Boolean,
filterPlaceholder: String, filterPlaceholder: String,
@ -103,6 +111,10 @@ export default {
type: String, type: String,
default: undefined default: undefined
}, },
chipIcon: {
type: String,
default: undefined
},
selectAll: { selectAll: {
type: Boolean, type: Boolean,
default: null default: null

View File

@ -130,34 +130,30 @@ export interface MultiSelectPassThroughOptions {
*/ */
label?: MultiSelectPassThroughOptionType; label?: MultiSelectPassThroughOptionType;
/** /**
* Used to pass attributes to the token's DOM element. * Used to pass attributes to the chip's DOM element.
*/ */
token?: MultiSelectPassThroughOptionType; chip?: MultiSelectPassThroughOptionType;
/** /**
* Used to pass attributes to the Chip. * Used to pass attributes to the Chip.
* @see {@link ChipPassThroughOptions} * @see {@link ChipPassThroughOptions}
*/ */
tokenLabel?: ChipPassThroughOptions<MultiSelectSharedPassThroughMethodOptions>; chipLabel?: ChipPassThroughOptions<MultiSelectSharedPassThroughMethodOptions>;
/** /**
* Used to pass attributes to the remove token icon's DOM element. * Used to pass attributes to the chip icon's DOM element.
*/ */
removeTokenIcon?: MultiSelectPassThroughOptionType; chipIcon?: MultiSelectPassThroughOptionType;
/** /**
* Used to pass attributes to the trigger's DOM element. * Used to pass attributes to the dropdown's DOM element.
*/ */
trigger?: MultiSelectPassThroughOptionType; dropdown?: MultiSelectPassThroughOptionType;
/**
* Used to pass attributes to the trigger icon's DOM element.
*/
triggerIcon?: MultiSelectPassThroughOptionType;
/** /**
* Used to pass attributes to the dropdown icon's DOM element. * Used to pass attributes to the dropdown icon's DOM element.
*/ */
dropdownIcon?: MultiSelectPassThroughOptionType; dropdownIcon?: MultiSelectPassThroughOptionType;
/** /**
* Used to pass attributes to the panel's DOM element. * Used to pass attributes to the overlay's DOM element.
*/ */
panel?: MultiSelectPassThroughOptionType; overlay?: MultiSelectPassThroughOptionType;
/** /**
* Used to pass attributes to the header's DOM element. * Used to pass attributes to the header's DOM element.
*/ */
@ -174,15 +170,15 @@ export interface MultiSelectPassThroughOptions {
* Used to pass attributes to the InputText component. * Used to pass attributes to the InputText component.
* @see {@link InputTextPassThroughOptions} * @see {@link InputTextPassThroughOptions}
*/ */
filterInput?: InputTextPassThroughOptions<MultiSelectSharedPassThroughMethodOptions>; filter?: InputTextPassThroughOptions<MultiSelectSharedPassThroughMethodOptions>;
/** /**
* Used to pass attributes to the filter icon's DOM element. * Used to pass attributes to the filter icon's DOM element.
*/ */
filterIcon?: MultiSelectPassThroughOptionType; filterIcon?: MultiSelectPassThroughOptionType;
/** /**
* Used to pass attributes to the wrapper's DOM element. * Used to pass attributes to the list container's DOM element.
*/ */
wrapper?: MultiSelectPassThroughOptionType; listContainer?: MultiSelectPassThroughOptionType;
/** /**
* Used to pass attributes to the VirtualScroller component. * Used to pass attributes to the VirtualScroller component.
* @see {@link VirtualScrollerPassThroughOptionType} * @see {@link VirtualScrollerPassThroughOptionType}
@ -193,21 +189,21 @@ export interface MultiSelectPassThroughOptions {
*/ */
list?: MultiSelectPassThroughOptionType; list?: MultiSelectPassThroughOptionType;
/** /**
* Used to pass attributes to the item group's DOM element. * Used to pass attributes to the option group's DOM element.
*/ */
itemGroup?: MultiSelectPassThroughOptionType; optionGroup?: MultiSelectPassThroughOptionType;
/**
* Used to pass attributes to the item's DOM element.
*/
item?: MultiSelectPassThroughOptionType;
/**
* Used to pass attributes to the item checkbox's DOM element.
*/
itemCheckbox?: MultiSelectPassThroughOptionType;
/** /**
* Used to pass attributes to the option's DOM element. * Used to pass attributes to the option's DOM element.
*/ */
option?: MultiSelectPassThroughOptionType; option?: MultiSelectPassThroughOptionType;
/**
* Used to pass attributes to the option label's DOM element.
*/
optionLabel?: MultiSelectPassThroughOptionType;
/**
* Used to pass attributes to the option checkbox's DOM element.
*/
optionCheckbox?: MultiSelectPassThroughOptionType;
/** /**
* Used to pass attributes to the emptyMessage's DOM element. * Used to pass attributes to the emptyMessage's DOM element.
*/ */
@ -361,13 +357,23 @@ export interface MultiSelectProps {
*/ */
inputId?: string | undefined; inputId?: string | undefined;
/** /**
* Inline style of the overlay panel. * @deprecated since v4.0. Use 'overlayStyle' instead.
* Inline style of the overlay.
*/ */
panelStyle?: any; panelStyle?: any;
/** /**
* Style class of the overlay panel. * @deprecated since v4.0. Use 'overlayClass' instead.
* Style class of the overlay.
*/ */
panelClass?: any; panelClass?: any;
/**
* Inline style of the overlay.
*/
overlayStyle?: any;
/**
* Style class of the overlay.
*/
overlayClass?: any;
/** /**
* A property to uniquely identify an option. * A property to uniquely identify an option.
*/ */
@ -454,9 +460,13 @@ export interface MultiSelectProps {
loadingIcon?: string | undefined; loadingIcon?: string | undefined;
/** /**
* Icon to display in chip remove action. * Icon to display in chip remove action.
* @deprecated since v3.27.0. Use 'removetokenicon' slot. * @deprecated since v4.0. Use 'chipicon' slot.
*/ */
removeTokenIcon?: string | undefined; removeTokenIcon?: string | undefined;
/**
* Icon to display in chip remove action.
*/
chipIcon?: string | undefined;
/** /**
* Whether all data is selected. * Whether all data is selected.
* @defaultValue false * @defaultValue false
@ -683,7 +693,8 @@ export interface MultiSelectSlots {
options: any[]; options: any[];
}): VNode[]; }): VNode[];
/** /**
* Custom remove token icon template. * @deprecated since v4.0. Use 'chipicon' slot instead.
* Custom chip icon template.
* @param {Object} scope - removetokenicon slot's params. * @param {Object} scope - removetokenicon slot's params.
*/ */
removetokenicon(scope: { removetokenicon(scope: {
@ -696,14 +707,41 @@ export interface MultiSelectSlots {
*/ */
item: any; item: any;
/** /**
* Remove token icon function. * Chip icon function.
* @param {Event} event - Browser event * @param {Event} event - Browser event
* @param {any} item - Item * @param {any} item - Item
* @deprecated since v3.39.0. Use 'removeCallback' property instead. * @deprecated since v3.39.0. Use 'removeCallback' property instead.
*/ */
onClick: (event: Event, item: any) => void; onClick: (event: Event, item: any) => void;
/** /**
* Remove token icon function. * Chip icon function.
* @param {Event} event - Browser event
* @param {any} item - Item
*/
removeCallback: (event: Event, item: any) => void;
}): VNode[];
/**
* Custom chip icon template.
* @param {Object} scope - chipicon slot's params.
*/
chipicon(scope: {
/**
* Style class of the loading icon.
*/
class: string;
/**
* Item of the token.
*/
item: any;
/**
* Chip icon function.
* @param {Event} event - Browser event
* @param {any} item - Item
* @deprecated since v3.39.0. Use 'removeCallback' property instead.
*/
onClick: (event: Event, item: any) => void;
/**
* Chip icon function.
* @param {Event} event - Browser event * @param {Event} event - Browser event
* @param {any} item - Item * @param {any} item - Item
*/ */

View File

@ -30,11 +30,12 @@
{{ label || 'empty' }} {{ label || 'empty' }}
</template> </template>
<template v-else-if="display === 'chip'"> <template v-else-if="display === 'chip'">
<span v-for="item of chipSelectedItems" :key="getLabelByValue(item)" :class="cx('token')" v-bind="ptm('token')"> <span v-for="item of chipSelectedItems" :key="getLabelByValue(item)" :class="cx('chip')" v-bind="ptm('chip')">
<slot name="chip" :value="item" :removeCallback="(event) => removeOption(event, item)"> <slot name="chip" :value="item" :removeCallback="(event) => removeOption(event, item)">
<Chip :class="cx('tokenLabel')" :label="getLabelByValue(item)" :removeIcon="removeTokenIcon" removable :unstyled="unstyled" @remove="removeOption($event, item)" :pt="ptm('tokenLabel')"> <!-- TODO: removetokenicon and removeTokenIcon deprecated since v4.0. Use chipicon slot and chipIcon prop-->
<Chip :class="cx('chipLabel')" :label="getLabelByValue(item)" :removeIcon="chipIcon || removeTokenIcon" removable :unstyled="unstyled" @remove="removeOption($event, item)" :pt="ptm('chipLabel')">
<template #removeicon> <template #removeicon>
<slot name="removetokenicon" :class="cx('removeTokenIcon')" :item="item" :removeCallback="(event) => removeOption(event, item)" /> <slot :name="$slots.removetokenicon ? 'removetokenicon' : 'chipicon'" :class="cx('chipIcon')" :item="item" :removeCallback="(event) => removeOption(event, item)" />
</template> </template>
</Chip> </Chip>
</slot> </slot>
@ -44,7 +45,7 @@
</slot> </slot>
</div> </div>
</div> </div>
<div :class="cx('trigger')" v-bind="ptm('trigger')"> <div :class="cx('dropdown')" v-bind="ptm('dropdown')">
<slot v-if="loading" name="loadingicon" :class="cx('loadingIcon')"> <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')" /> <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')" /> <SpinnerIcon v-else :class="cx('loadingIcon')" spin aria-hidden="true" v-bind="ptm('loadingIcon')" />
@ -55,7 +56,7 @@
</div> </div>
<Portal :appendTo="appendTo"> <Portal :appendTo="appendTo">
<transition name="p-connected-overlay" @enter="onOverlayEnter" @after-enter="onOverlayAfterEnter" @leave="onOverlayLeave" @after-leave="onOverlayAfterLeave" v-bind="ptm('transition')"> <transition name="p-connected-overlay" @enter="onOverlayEnter" @after-enter="onOverlayAfterEnter" @leave="onOverlayLeave" @after-leave="onOverlayAfterLeave" v-bind="ptm('transition')">
<div v-if="overlayVisible" :ref="overlayRef" :style="panelStyle" :class="[cx('panel'), panelClass]" @click="onOverlayClick" @keydown="onOverlayKeyDown" v-bind="ptm('panel')"> <div v-if="overlayVisible" :ref="overlayRef" :style="[panelStyle, overlayStyle]" :class="[cx('overlay'), panelClass, overlayClass]" @click="onOverlayClick" @keydown="onOverlayKeyDown" v-bind="ptm('overlay')">
<span <span
ref="firstHiddenFocusableElementOnOverlay" ref="firstHiddenFocusableElementOnOverlay"
role="presentation" role="presentation"
@ -91,7 +92,7 @@
:value="filterValue" :value="filterValue"
@vue:mounted="onFilterUpdated" @vue:mounted="onFilterUpdated"
@vue:updated="onFilterUpdated" @vue:updated="onFilterUpdated"
:class="cx('filterInput')" :class="cx('filter')"
:placeholder="filterPlaceholder" :placeholder="filterPlaceholder"
:invalid="invalid" :invalid="invalid"
:disabled="disabled" :disabled="disabled"
@ -104,7 +105,7 @@
@keydown="onFilterKeyDown" @keydown="onFilterKeyDown"
@blur="onFilterBlur" @blur="onFilterBlur"
@input="onFilterChange" @input="onFilterChange"
:pt="ptm('filterInput')" :pt="ptm('filter')"
/> />
<slot name="filtericon" :class="cx('filterIcon')"> <slot name="filtericon" :class="cx('filterIcon')">
<component :is="filterIcon ? 'span' : 'SearchIcon'" :class="[cx('filterIcon'), filterIcon]" v-bind="ptm('filterIcon')" /> <component :is="filterIcon ? 'span' : 'SearchIcon'" :class="[cx('filterIcon'), filterIcon]" v-bind="ptm('filterIcon')" />
@ -114,12 +115,19 @@
{{ filterResultMessageText }} {{ filterResultMessageText }}
</span> </span>
</div> </div>
<div :class="cx('wrapper')" :style="{ 'max-height': virtualScrollerDisabled ? scrollHeight : '' }" v-bind="ptm('wrapper')"> <div :class="cx('listContainer')" :style="{ 'max-height': virtualScrollerDisabled ? scrollHeight : '' }" v-bind="ptm('listContainer')">
<VirtualScroller :ref="virtualScrollerRef" v-bind="virtualScrollerOptions" :items="visibleOptions" :style="{ height: scrollHeight }" :tabindex="-1" :disabled="virtualScrollerDisabled" :pt="ptm('virtualScroller')"> <VirtualScroller :ref="virtualScrollerRef" v-bind="virtualScrollerOptions" :items="visibleOptions" :style="{ height: scrollHeight }" :tabindex="-1" :disabled="virtualScrollerDisabled" :pt="ptm('virtualScroller')">
<template v-slot:content="{ styleClass, contentRef, items, getItemOptions, contentStyle, itemSize }"> <template v-slot:content="{ styleClass, contentRef, items, getItemOptions, contentStyle, itemSize }">
<ul :ref="(el) => listRef(el, contentRef)" :id="id + '_list'" :class="[cx('list'), styleClass]" :style="contentStyle" role="listbox" aria-multiselectable="true" :aria-label="listAriaLabel" v-bind="ptm('list')"> <ul :ref="(el) => listRef(el, contentRef)" :id="id + '_list'" :class="[cx('list'), styleClass]" :style="contentStyle" role="listbox" aria-multiselectable="true" :aria-label="listAriaLabel" v-bind="ptm('list')">
<template v-for="(option, i) of items" :key="getOptionRenderKey(option, getOptionIndex(i, getItemOptions))"> <template v-for="(option, i) of items" :key="getOptionRenderKey(option, getOptionIndex(i, getItemOptions))">
<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')"> <li
v-if="isOptionGroup(option)"
:id="id + '_' + getOptionIndex(i, getItemOptions)"
:style="{ height: itemSize ? itemSize + 'px' : undefined }"
:class="cx('optionGroup')"
role="option"
v-bind="ptm('optionGroup')"
>
<slot name="optiongroup" :option="option.optionGroup" :index="getOptionIndex(i, getItemOptions)">{{ getOptionGroupLabel(option.optionGroup) }}</slot> <slot name="optiongroup" :option="option.optionGroup" :index="getOptionIndex(i, getItemOptions)">{{ getOptionGroupLabel(option.optionGroup) }}</slot>
</li> </li>
<li <li
@ -127,7 +135,7 @@
:id="id + '_' + getOptionIndex(i, getItemOptions)" :id="id + '_' + getOptionIndex(i, getItemOptions)"
v-ripple v-ripple
:style="{ height: itemSize ? itemSize + 'px' : undefined }" :style="{ height: itemSize ? itemSize + 'px' : undefined }"
:class="cx('item', { option, index: i, getItemOptions })" :class="cx('option', { option, index: i, getItemOptions })"
role="option" role="option"
:aria-label="getOptionLabel(option)" :aria-label="getOptionLabel(option)"
:aria-selected="isSelected(option)" :aria-selected="isSelected(option)"
@ -136,24 +144,24 @@
:aria-posinset="getAriaPosInset(getOptionIndex(i, getItemOptions))" :aria-posinset="getAriaPosInset(getOptionIndex(i, getItemOptions))"
@click="onOptionSelect($event, option, getOptionIndex(i, getItemOptions), true)" @click="onOptionSelect($event, option, getOptionIndex(i, getItemOptions), true)"
@mousemove="onOptionMouseMove($event, getOptionIndex(i, getItemOptions))" @mousemove="onOptionMouseMove($event, getOptionIndex(i, getItemOptions))"
v-bind="getCheckboxPTOptions(option, getItemOptions, i, 'item')" v-bind="getCheckboxPTOptions(option, getItemOptions, i, 'option')"
:data-p-highlight="isSelected(option)" :data-p-highlight="isSelected(option)"
:data-p-focused="focusedOptionIndex === getOptionIndex(i, getItemOptions)" :data-p-focused="focusedOptionIndex === getOptionIndex(i, getItemOptions)"
:data-p-disabled="isOptionDisabled(option)" :data-p-disabled="isOptionDisabled(option)"
> >
<Checkbox :modelValue="isSelected(option)" :binary="true" :tabindex="-1" :variant="variant" :unstyled="unstyled" :pt="getCheckboxPTOptions(option, getItemOptions, i, 'itemCheckbox')"> <Checkbox :modelValue="isSelected(option)" :binary="true" :tabindex="-1" :variant="variant" :unstyled="unstyled" :pt="getCheckboxPTOptions(option, getItemOptions, i, 'optionCheckbox')">
<template #icon="slotProps"> <template #icon="slotProps">
<component v-if="$slots.itemcheckboxicon" :is="$slots.itemcheckboxicon" :checked="slotProps.checked" :class="slotProps.class" /> <component v-if="$slots.optioncheckboxicon || $slots.itemcheckboxicon" :is="$slots.optioncheckboxicon || $slots.itemcheckboxicon" :checked="slotProps.checked" :class="slotProps.class" />
<component <component
v-else-if="slotProps.checked" v-else-if="slotProps.checked"
:is="checkboxIcon ? 'span' : 'CheckIcon'" :is="checkboxIcon ? 'span' : 'CheckIcon'"
:class="[slotProps.class, { [checkboxIcon]: slotProps.checked }]" :class="[slotProps.class, { [checkboxIcon]: slotProps.checked }]"
v-bind="getCheckboxPTOptions(option, getItemOptions, i, 'itemCheckbox.icon')" v-bind="getCheckboxPTOptions(option, getItemOptions, i, 'optionCheckbox.icon')"
/> />
</template> </template>
</Checkbox> </Checkbox>
<slot name="option" :option="option" :index="getOptionIndex(i, getItemOptions)"> <slot name="option" :option="option" :index="getOptionIndex(i, getItemOptions)">
<span v-bind="ptm('option')">{{ getOptionLabel(option) }}</span> <span v-bind="ptm('optionLabel')">{{ getOptionLabel(option) }}</span>
</slot> </slot>
</li> </li>
</template> </template>

View File

@ -76,7 +76,7 @@ const theme = ({ dt }) => `
color: ${dt('multiselect.disabled.color')}; color: ${dt('multiselect.disabled.color')};
} }
.p-inputwrapper-filled.p-multiselect.p-multiselect-display-chip .p-multiselect-label { .p-inputlistContainer-filled.p-multiselect.p-multiselect-display-chip .p-multiselect-label {
padding: 0.25rem 0.25rem; padding: 0.25rem 0.25rem;
} }
@ -209,15 +209,15 @@ const inlineStyles = {
const classes = { const classes = {
root: ({ instance, props }) => [ root: ({ instance, props }) => [
'p-multiselect p-component p-inputwrapper', 'p-multiselect p-component p-inputlistContainer',
{ {
'p-multiselect-display-chip': props.display === 'chip', 'p-multiselect-display-chip': props.display === 'chip',
'p-disabled': props.disabled, 'p-disabled': props.disabled,
'p-invalid': props.invalid, 'p-invalid': props.invalid,
'p-variant-filled': props.variant ? props.variant === 'filled' : instance.$primevue.config.inputStyle === 'filled', 'p-variant-filled': props.variant ? props.variant === 'filled' : instance.$primevue.config.inputStyle === 'filled',
'p-focus': instance.focused, 'p-focus': instance.focused,
'p-inputwrapper-filled': props.modelValue && props.modelValue.length, 'p-inputlistContainer-filled': props.modelValue && props.modelValue.length,
'p-inputwrapper-focus': instance.focused || instance.overlayVisible, 'p-inputlistContainer-focus': instance.focused || instance.overlayVisible,
'p-multiselect-open': instance.overlayVisible 'p-multiselect-open': instance.overlayVisible
} }
], ],
@ -229,13 +229,13 @@ const classes = {
'p-multiselect-label-empty': !props.placeholder && (!props.modelValue || props.modelValue.length === 0) 'p-multiselect-label-empty': !props.placeholder && (!props.modelValue || props.modelValue.length === 0)
} }
], ],
token: 'p-multiselect-chip', chip: 'p-multiselect-chip',
tokenLabel: 'p-multiselect-chip-label', chipLabel: 'p-multiselect-chip-label',
removeTokenIcon: 'p-multiselect-chip-icon', chipIcon: 'p-multiselect-chip-icon',
trigger: 'p-multiselect-dropdown', dropdown: 'p-multiselect-dropdown',
loadingIcon: 'p-multiselect-loading-icon', loadingIcon: 'p-multiselect-loading-icon',
dropdownIcon: 'p-multiselect-dropdown-icon', dropdownIcon: 'p-multiselect-dropdown-icon',
panel: ({ instance }) => [ overlay: ({ instance }) => [
'p-multiselect-overlay p-component', 'p-multiselect-overlay p-component',
{ {
'p-ripple-disabled': instance.$primevue.config.ripple === false 'p-ripple-disabled': instance.$primevue.config.ripple === false
@ -243,12 +243,12 @@ const classes = {
], ],
header: 'p-multiselect-header', header: 'p-multiselect-header',
filterContainer: 'p-multiselect-filter-container', filterContainer: 'p-multiselect-filter-container',
filterInput: 'p-multiselect-filter', filter: 'p-multiselect-filter',
filterIcon: 'p-multiselect-filter-icon', filterIcon: 'p-multiselect-filter-icon',
wrapper: 'p-multiselect-list-container', listContainer: 'p-multiselect-list-container',
list: 'p-multiselect-list', list: 'p-multiselect-list',
itemGroup: 'p-multiselect-option-group', optionGroup: 'p-multiselect-option-group',
item: ({ instance, option, index, getItemOptions, props }) => [ option: ({ instance, option, index, getItemOptions, props }) => [
'p-multiselect-option', 'p-multiselect-option',
{ {
'p-multiselect-option-selected': instance.isSelected(option) && props.highlightOnSelect, 'p-multiselect-option-selected': instance.isSelected(option) && props.highlightOnSelect,