Refactor #5612 - Dropdown / Select
parent
def5d060c0
commit
55ae9908d1
|
@ -2,735 +2,76 @@
|
|||
*
|
||||
* Dropdown also known as Select, is used to choose an item from a collection of options.
|
||||
*
|
||||
* [Live Demo](https://www.primevue.org/dropdown/)
|
||||
* [Live Demo](https://www.primevue.org/select/)
|
||||
*
|
||||
* @module dropdown
|
||||
*
|
||||
*/
|
||||
import { TransitionProps, VNode } from 'vue';
|
||||
import { ComponentHooks } from '../basecomponent';
|
||||
import { PassThroughOptions } from '../passthrough';
|
||||
import { ClassComponent, DesignToken, GlobalComponentConstructor, HintedString, PassThrough } from '../ts-helpers';
|
||||
import { VirtualScrollerItemOptions, VirtualScrollerPassThroughOptionType, VirtualScrollerProps } from '../virtualscroller';
|
||||
|
||||
export declare type DropdownPassThroughOptionType<T = any> = DropdownPassThroughAttributes | ((options: DropdownPassThroughMethodOptions<T>) => DropdownPassThroughAttributes | string) | string | null | undefined;
|
||||
|
||||
export declare type DropdownPassThroughTransitionType<T = any> = TransitionProps | ((options: DropdownPassThroughMethodOptions<T>) => TransitionProps) | undefined;
|
||||
import 'vue';
|
||||
import * as Select from '../select';
|
||||
import { ClassComponent, GlobalComponentConstructor } from '../ts-helpers';
|
||||
|
||||
/**
|
||||
* Custom passthrough(pt) option method.
|
||||
*/
|
||||
export interface DropdownPassThroughMethodOptions<T> {
|
||||
/**
|
||||
* Defines instance.
|
||||
*/
|
||||
instance: any;
|
||||
/**
|
||||
* Defines valid properties.
|
||||
*/
|
||||
props: DropdownProps;
|
||||
/**
|
||||
* Defines current inline state.
|
||||
*/
|
||||
state: DropdownState;
|
||||
/**
|
||||
* Defines parent instance.
|
||||
*/
|
||||
parent: T | any;
|
||||
/**
|
||||
* Defines current options.
|
||||
*/
|
||||
context: DropdownContext;
|
||||
/**
|
||||
* Defines passthrough(pt) options in global config.
|
||||
*/
|
||||
global: object | undefined;
|
||||
}
|
||||
export interface DropdownPassThroughMethodOptions<T> extends Select.SelectPassThroughMethodOptions<T> {}
|
||||
|
||||
/**
|
||||
* Custom change event.
|
||||
* @see {@link DropdownEmits.change}
|
||||
*/
|
||||
export interface DropdownChangeEvent {
|
||||
/**
|
||||
* Browser event.
|
||||
*/
|
||||
originalEvent: Event;
|
||||
/**
|
||||
* Selected option value
|
||||
*/
|
||||
value: any;
|
||||
}
|
||||
export interface DropdownChangeEvent extends Select.SelectChangeEvent {}
|
||||
|
||||
/**
|
||||
* Custom filter event.
|
||||
* @see {@link DropdownEmits.filter}
|
||||
*/
|
||||
export interface DropdownFilterEvent {
|
||||
/**
|
||||
* Browser event.
|
||||
*/
|
||||
originalEvent: Event;
|
||||
/**
|
||||
* Filter value
|
||||
*/
|
||||
value: any;
|
||||
}
|
||||
export interface DropdownFilterEvent extends Select.SelectFilterEvent {}
|
||||
|
||||
/**
|
||||
* Custom passthrough(pt) options.
|
||||
* @see {@link DropdownProps.pt}
|
||||
*/
|
||||
export interface DropdownPassThroughOptions<T = any> {
|
||||
/**
|
||||
* Used to pass attributes to the root's DOM element.
|
||||
*/
|
||||
root?: DropdownPassThroughOptionType<T>;
|
||||
/**
|
||||
* Used to pass attributes to the input's DOM element.
|
||||
*/
|
||||
input?: DropdownPassThroughOptionType<T>;
|
||||
/**
|
||||
* Used to pass attributes to the clear icon's DOM element.
|
||||
*/
|
||||
clearIcon?: DropdownPassThroughOptionType<T>;
|
||||
/**
|
||||
* Used to pass attributes to the trigger' DOM element.
|
||||
*/
|
||||
trigger?: DropdownPassThroughOptionType<T>;
|
||||
/**
|
||||
* Used to pass attributes to the loading icon's DOM element.
|
||||
*/
|
||||
loadingIcon?: DropdownPassThroughOptionType<T>;
|
||||
/**
|
||||
* Used to pass attributes to the panel's DOM element.
|
||||
*/
|
||||
panel?: DropdownPassThroughOptionType<T>;
|
||||
/**
|
||||
* Used to pass attributes to the header's DOM element.
|
||||
*/
|
||||
header?: DropdownPassThroughOptionType<T>;
|
||||
/**
|
||||
* Used to pass attributes to the filter container's DOM element.
|
||||
*/
|
||||
filterContainer?: DropdownPassThroughOptionType<T>;
|
||||
/**
|
||||
* Used to pass attributes to the filter input's DOM element.
|
||||
*/
|
||||
filterInput?: DropdownPassThroughOptionType<T>;
|
||||
/**
|
||||
* Used to pass attributes to the filter icon's DOM element.
|
||||
*/
|
||||
filterIcon?: DropdownPassThroughOptionType<T>;
|
||||
/**
|
||||
* Used to pass attributes to the wrapper's DOM element.
|
||||
*/
|
||||
wrapper?: DropdownPassThroughOptionType<T>;
|
||||
/**
|
||||
* Used to pass attributes to the VirtualScroller component.
|
||||
* @see {@link VirtualScrollerPassThroughOptionType}
|
||||
*/
|
||||
virtualScroller?: VirtualScrollerPassThroughOptionType;
|
||||
/**
|
||||
* Used to pass attributes to the list's DOM element.
|
||||
*/
|
||||
list?: DropdownPassThroughOptionType<T>;
|
||||
/**
|
||||
* Used to pass attributes to the item group's DOM element.
|
||||
*/
|
||||
itemGroup?: DropdownPassThroughOptionType<T>;
|
||||
/**
|
||||
* Used to pass attributes to the item group label's DOM element.
|
||||
*/
|
||||
itemGroupLabel?: DropdownPassThroughOptionType<T>;
|
||||
/**
|
||||
* Used to pass attributes to the item's DOM element.
|
||||
*/
|
||||
item?: DropdownPassThroughOptionType<T>;
|
||||
/**
|
||||
* Used to pass attributes to the item label's DOM element.
|
||||
*/
|
||||
itemLabel?: DropdownPassThroughOptionType<T>;
|
||||
/**
|
||||
* Used to pass attributes to the check icon's DOM element.
|
||||
*/
|
||||
checkIcon?: DropdownPassThroughOptionType<T>;
|
||||
/**
|
||||
* Used to pass attributes to the bank icon's DOM element.
|
||||
*/
|
||||
blankIcon?: DropdownPassThroughOptionType<T>;
|
||||
/**
|
||||
* Used to pass attributes to the empty message's DOM element.
|
||||
*/
|
||||
emptyMessage?: DropdownPassThroughOptionType<T>;
|
||||
/**
|
||||
* Used to pass attributes to the hidden first focusable element's DOM element.
|
||||
*/
|
||||
hiddenFirstFocusableEl?: DropdownPassThroughOptionType<T>;
|
||||
/**
|
||||
* Used to pass attributes to the hidden filter result's DOM element.
|
||||
*/
|
||||
hiddenFilterResult?: DropdownPassThroughOptionType<T>;
|
||||
/**
|
||||
* Used to pass attributes to the hidden empty message's DOM element.
|
||||
*/
|
||||
hiddenEmptyMessage?: DropdownPassThroughOptionType<T>;
|
||||
/**
|
||||
* Used to pass attributes to the hidden selected message's DOM element.
|
||||
*/
|
||||
hiddenSelectedMessage?: DropdownPassThroughOptionType<T>;
|
||||
/**
|
||||
* Used to pass attributes to the hidden last focusable element's DOM element.
|
||||
*/
|
||||
hiddenLastFocusableEl?: DropdownPassThroughOptionType<T>;
|
||||
/**
|
||||
* Used to manage all lifecycle hooks.
|
||||
* @see {@link BaseComponent.ComponentHooks}
|
||||
*/
|
||||
hooks?: ComponentHooks;
|
||||
/**
|
||||
* Used to control Vue Transition API.
|
||||
*/
|
||||
transition?: DropdownPassThroughTransitionType;
|
||||
}
|
||||
export interface DropdownPassThroughOptions<T = any> extends Select.SelectPassThroughOptions<T> {}
|
||||
|
||||
/**
|
||||
* Custom passthrough attributes for each DOM elements
|
||||
*/
|
||||
export interface DropdownPassThroughAttributes {
|
||||
[key: string]: any;
|
||||
}
|
||||
export interface DropdownPassThroughAttributes extends Select.SelectPassThroughAttributes {}
|
||||
|
||||
/**
|
||||
* Defines current inline state in Dropdown component.
|
||||
*/
|
||||
export interface DropdownState {
|
||||
/**
|
||||
* Current id state as a string.
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* Current focused state as a boolean.
|
||||
* @defaultValue false
|
||||
*/
|
||||
focused: boolean;
|
||||
/**
|
||||
* Current focused item index as a number.
|
||||
* @defaultValue -1
|
||||
*/
|
||||
focusedOptionIndex: number;
|
||||
/**
|
||||
* Current filter value state as a string.
|
||||
*/
|
||||
filterValue: string;
|
||||
/**
|
||||
* Current overlay visible state as a boolean.
|
||||
* @defaultValue false
|
||||
*/
|
||||
overlayVisible: boolean;
|
||||
}
|
||||
export interface DropdownState extends Select.SelectState {}
|
||||
|
||||
/**
|
||||
* Defines current options in Dropdown component.
|
||||
*/
|
||||
export interface DropdownContext {
|
||||
/**
|
||||
* Current item option.
|
||||
*/
|
||||
option: any;
|
||||
/**
|
||||
* Current item index.
|
||||
*/
|
||||
index: number;
|
||||
/**
|
||||
* Current selection state of the item as a boolean.
|
||||
* @defaultValue false
|
||||
*/
|
||||
selected: boolean;
|
||||
/**
|
||||
* Current focus state of the item as a boolean.
|
||||
* @defaultValue false
|
||||
*/
|
||||
focused: boolean;
|
||||
/**
|
||||
* Current disabled state of the item as a boolean.
|
||||
* @defaultValue false
|
||||
*/
|
||||
disabled: boolean;
|
||||
}
|
||||
export interface DropdownContext extends Select.SelectContext {}
|
||||
|
||||
/**
|
||||
* Defines valid properties in Dropdown component.
|
||||
*/
|
||||
export interface DropdownProps {
|
||||
/**
|
||||
* Value of the component.
|
||||
*/
|
||||
modelValue?: any;
|
||||
/**
|
||||
* An array of select items to display as the available options.
|
||||
*/
|
||||
options?: any[];
|
||||
/**
|
||||
* Property name or getter function to use as the label of an option.
|
||||
*/
|
||||
optionLabel?: string | ((data: any) => string) | undefined;
|
||||
/**
|
||||
* Property name or getter function to use as the value of an option, defaults to the option itself when not defined.
|
||||
*/
|
||||
optionValue?: string | ((data: any) => any) | undefined;
|
||||
/**
|
||||
* Property name or getter function to use as the disabled flag of an option, defaults to false when not defined.
|
||||
*/
|
||||
optionDisabled?: string | ((data: any) => boolean) | undefined;
|
||||
/**
|
||||
* Property name or getter function to use as the label of an option group.
|
||||
*/
|
||||
optionGroupLabel?: string | ((data: any) => string) | undefined;
|
||||
/**
|
||||
* Property name or getter function that refers to the children options of option group.
|
||||
*/
|
||||
optionGroupChildren?: string | ((data: any) => any[]) | undefined;
|
||||
/**
|
||||
* Height of the viewport, a scrollbar is defined if height of list exceeds this value.
|
||||
* @defaultValue 14rem
|
||||
*/
|
||||
scrollHeight?: string | undefined;
|
||||
/**
|
||||
* When specified, displays a filter input at header.
|
||||
* @defaultValue false
|
||||
*/
|
||||
filter?: boolean | undefined;
|
||||
/**
|
||||
* Placeholder text to show when filter input is empty.
|
||||
*/
|
||||
filterPlaceholder?: string | undefined;
|
||||
/**
|
||||
* Locale to use in filtering. The default locale is the host environment's current locale.
|
||||
*/
|
||||
filterLocale?: string | undefined;
|
||||
/**
|
||||
* Defines the filtering algorithm to use when searching the options.
|
||||
* @defaultValue contains
|
||||
*/
|
||||
filterMatchMode?: HintedString<'contains' | 'startsWith' | 'endsWith'> | undefined;
|
||||
/**
|
||||
* Fields used when filtering the options, defaults to optionLabel.
|
||||
*/
|
||||
filterFields?: string[] | undefined;
|
||||
/**
|
||||
* When present, custom value instead of predefined options can be entered using the editable input field.
|
||||
* @defaultValue false
|
||||
*/
|
||||
editable?: boolean | undefined;
|
||||
/**
|
||||
* Default text to display when no option is selected.
|
||||
*/
|
||||
placeholder?: string | undefined;
|
||||
/**
|
||||
* When present, it specifies that the component should have invalid state style.
|
||||
* @defaultValue false
|
||||
*/
|
||||
invalid?: boolean | undefined;
|
||||
/**
|
||||
* When present, it specifies that the component should be disabled.
|
||||
* @defaultValue false
|
||||
*/
|
||||
disabled?: boolean | undefined;
|
||||
/**
|
||||
* Specifies the input variant of the component.
|
||||
* @defaultValue outlined
|
||||
*/
|
||||
variant?: 'outlined' | 'filled' | undefined;
|
||||
/**
|
||||
* A property to uniquely identify an option.
|
||||
*/
|
||||
dataKey?: string | undefined;
|
||||
/**
|
||||
* When enabled, a clear icon is displayed to clear the value.
|
||||
* @defaultValue false
|
||||
*/
|
||||
showClear?: boolean | undefined;
|
||||
/**
|
||||
* Identifier of the underlying input element.
|
||||
*/
|
||||
inputId?: string | undefined;
|
||||
/**
|
||||
* Inline style of the input field.
|
||||
*/
|
||||
inputStyle?: object | undefined;
|
||||
/**
|
||||
* Style class of the input field.
|
||||
*/
|
||||
inputClass?: string | object | undefined;
|
||||
/**
|
||||
* Inline style of the overlay panel.
|
||||
*/
|
||||
panelStyle?: object | undefined;
|
||||
/**
|
||||
* Style class of the overlay panel.
|
||||
*/
|
||||
panelClass?: string | object | undefined;
|
||||
/**
|
||||
* A valid query selector or an HTMLElement to specify where the overlay gets attached.
|
||||
* @defaultValue body
|
||||
*/
|
||||
appendTo?: HintedString<'body' | 'self'> | undefined | HTMLElement;
|
||||
/**
|
||||
* Whether the dropdown is in loading state.
|
||||
* @defaultValue false
|
||||
*/
|
||||
loading?: boolean | undefined;
|
||||
/**
|
||||
* Icon to display in clear button.
|
||||
* @deprecated since v3.27.0. Use 'clearicon' slot.
|
||||
*/
|
||||
clearIcon?: string | undefined;
|
||||
/**
|
||||
* Icon to display in the dropdown.
|
||||
* @deprecated since v3.27.0. Use 'dropdownicon' slot.
|
||||
*/
|
||||
dropdownIcon?: string | undefined;
|
||||
/**
|
||||
* Icon to display in filter input.
|
||||
* @deprecated since v3.27.0. Use 'filtericon' slot.
|
||||
*/
|
||||
filterIcon?: string | undefined;
|
||||
/**
|
||||
* Icon to display in loading state.
|
||||
* @deprecated since v3.27.0. Use 'loadingicon' slot.
|
||||
*/
|
||||
loadingIcon?: string | undefined;
|
||||
/**
|
||||
* Clears the filter value when hiding the dropdown.
|
||||
* @defaultValue false
|
||||
*/
|
||||
resetFilterOnHide?: boolean;
|
||||
/**
|
||||
* Clears the filter value when clicking on the clear icon.
|
||||
* @defaultValue false
|
||||
*/
|
||||
resetFilterOnClear?: boolean;
|
||||
/**
|
||||
* Whether to use the virtualScroller feature. The properties of VirtualScroller component can be used like an object in it.
|
||||
*/
|
||||
virtualScrollerOptions?: VirtualScrollerProps;
|
||||
/**
|
||||
* Whether to focus on the first visible or selected element when the overlay panel is shown.
|
||||
* @defaultValue false
|
||||
*/
|
||||
autoOptionFocus?: boolean | undefined;
|
||||
/**
|
||||
* Whether to focus on the filter element when the overlay panel is shown.
|
||||
* @defaultValue false
|
||||
*/
|
||||
autoFilterFocus?: boolean | undefined;
|
||||
/**
|
||||
* When enabled, the focused option is selected.
|
||||
* @defaultValue false
|
||||
*/
|
||||
selectOnFocus?: boolean | undefined;
|
||||
/**
|
||||
* When enabled, the focus is placed on the hovered option.
|
||||
* @defaultValue true
|
||||
*/
|
||||
focusOnHover?: boolean | undefined;
|
||||
/**
|
||||
* Whether the selected option will be add highlight class.
|
||||
* @defaultValue true
|
||||
*/
|
||||
highlightOnSelect?: boolean | undefined;
|
||||
/**
|
||||
* Whether the selected option will be shown with a check mark.
|
||||
* @defaultValue false
|
||||
*/
|
||||
checkmark?: boolean | undefined;
|
||||
/**
|
||||
* Text to be displayed in hidden accessible field when filtering returns any results. Defaults to value from PrimeVue locale configuration.
|
||||
* @defaultValue '{0} results are available'
|
||||
*/
|
||||
filterMessage?: string | undefined;
|
||||
/**
|
||||
* Text to be displayed in hidden accessible field when options are selected. Defaults to value from PrimeVue locale configuration.
|
||||
* @defaultValue '{0} items selected'
|
||||
*/
|
||||
selectionMessage?: string | undefined;
|
||||
/**
|
||||
* Text to be displayed in hidden accessible field when any option is not selected. Defaults to value from PrimeVue locale configuration.
|
||||
* @defaultValue No selected item
|
||||
*/
|
||||
emptySelectionMessage?: string | undefined;
|
||||
/**
|
||||
* Text to display when filtering does not return any results. Defaults to value from PrimeVue locale configuration.
|
||||
* @defaultValue No results found
|
||||
*/
|
||||
emptyFilterMessage?: string | undefined;
|
||||
/**
|
||||
* Text to display when there are no options available. Defaults to value from PrimeVue locale configuration.
|
||||
* @defaultValue No results found
|
||||
*/
|
||||
emptyMessage?: string | undefined;
|
||||
/**
|
||||
* Index of the element in tabbing order.
|
||||
*/
|
||||
tabindex?: number | string | undefined;
|
||||
/**
|
||||
* Defines a string value that labels an interactive element.
|
||||
*/
|
||||
ariaLabel?: string | undefined;
|
||||
/**
|
||||
* Identifier of the underlying input element.
|
||||
*/
|
||||
ariaLabelledby?: string | undefined;
|
||||
/**
|
||||
* It generates scoped CSS variables using design tokens for the component.
|
||||
*/
|
||||
dt?: DesignToken<any>;
|
||||
/**
|
||||
* Used to pass attributes to DOM elements inside the component.
|
||||
* @type {DropdownPassThroughOptions}
|
||||
*/
|
||||
pt?: PassThrough<DropdownPassThroughOptions>;
|
||||
/**
|
||||
* Used to configure passthrough(pt) options of the component.
|
||||
* @type {PassThroughOptions}
|
||||
*/
|
||||
ptOptions?: PassThroughOptions;
|
||||
/**
|
||||
* When enabled, it removes component related styles in the core.
|
||||
* @defaultValue false
|
||||
*/
|
||||
unstyled?: boolean;
|
||||
}
|
||||
export interface DropdownProps extends Select.SelectProps {}
|
||||
|
||||
/**
|
||||
* Defines valid slots in Dropdown component.
|
||||
*/
|
||||
export interface DropdownSlots {
|
||||
/**
|
||||
* Custom value template.
|
||||
* @param {Object} scope - value slot's params.
|
||||
*/
|
||||
value(scope: {
|
||||
/**
|
||||
* Value of the component
|
||||
*/
|
||||
value: any;
|
||||
/**
|
||||
* Placeholder prop value
|
||||
*/
|
||||
placeholder: string;
|
||||
}): VNode[];
|
||||
/**
|
||||
* Custom indicator template.
|
||||
* @deprecated since v3.27.0. Use 'dropdownicon or loadingicon' slots.
|
||||
*/
|
||||
indicator(): VNode[];
|
||||
/**
|
||||
* Custom header template of panel.
|
||||
* @param {Object} scope - header slot's params.
|
||||
*/
|
||||
header(scope: {
|
||||
/**
|
||||
* Value of the component
|
||||
*/
|
||||
value: any;
|
||||
/**
|
||||
* Displayed options
|
||||
*/
|
||||
options: any[];
|
||||
}): VNode[];
|
||||
/**
|
||||
* Custom footer template of panel.
|
||||
* @param {Object} scope - footer slot's params.
|
||||
*/
|
||||
footer(scope: {
|
||||
/**
|
||||
* Value of the component
|
||||
*/
|
||||
value: any;
|
||||
/**
|
||||
* Displayed options
|
||||
*/
|
||||
options: any[];
|
||||
}): VNode[];
|
||||
/**
|
||||
* Custom option template.
|
||||
* @param {Object} scope - option slot's params.
|
||||
*/
|
||||
option(scope: {
|
||||
/**
|
||||
* Option instance
|
||||
*/
|
||||
option: any;
|
||||
/**
|
||||
* Index of the option
|
||||
*/
|
||||
index: number;
|
||||
}): VNode[];
|
||||
/**
|
||||
* Custom option group template.
|
||||
* @param {Object} scope - option group slot's params.
|
||||
*/
|
||||
optiongroup(scope: {
|
||||
/**
|
||||
* Option instance
|
||||
*/
|
||||
option: any;
|
||||
/**
|
||||
* Index of the option
|
||||
*/
|
||||
index: number;
|
||||
}): VNode[];
|
||||
/**
|
||||
* Custom empty filter template.
|
||||
*/
|
||||
emptyfilter(): VNode[];
|
||||
/**
|
||||
* Custom empty template.
|
||||
*/
|
||||
empty(): VNode[];
|
||||
/**
|
||||
* Custom content template.
|
||||
* @param {Object} scope - content slot's params.
|
||||
*/
|
||||
content(scope: {
|
||||
/**
|
||||
* An array of objects to display for virtualscroller
|
||||
*/
|
||||
items: any;
|
||||
/**
|
||||
* Style class of the component
|
||||
*/
|
||||
styleClass: string;
|
||||
/**
|
||||
* Referance of the content
|
||||
* @param {HTMLElement} el - Element of 'ref' property
|
||||
*/
|
||||
contentRef: (el: any) => void;
|
||||
/**
|
||||
* Options of the items
|
||||
* @param {number} index - Rendered index
|
||||
* @return {@link VirtualScrollerItemOptions}
|
||||
*/
|
||||
getItemOptions: (index: number) => VirtualScrollerItemOptions;
|
||||
}): VNode[];
|
||||
/**
|
||||
* Custom loader template.
|
||||
* @param {Object} scope - loader slot's params.
|
||||
*/
|
||||
loader(scope: {
|
||||
/**
|
||||
* Options of the loader items for virtualscroller
|
||||
*/
|
||||
options: any[];
|
||||
}): VNode[];
|
||||
/**
|
||||
* Custom clear icon template.
|
||||
* @param {Object} scope - clear icon slot's params.
|
||||
*/
|
||||
clearicon(scope: {
|
||||
/**
|
||||
* Style class of the clear icon
|
||||
*/
|
||||
class: any;
|
||||
/**
|
||||
* Clear icon click function.
|
||||
* @param {Event} event - Browser event
|
||||
* @deprecated since v3.39.0. Use 'clearCallback' property instead.
|
||||
*/
|
||||
onClick: (event: Event) => void;
|
||||
/**
|
||||
* Clear icon click function.
|
||||
* @param {Event} event - Browser event
|
||||
*/
|
||||
clearCallback: (event: Event) => void;
|
||||
}): VNode[];
|
||||
/**
|
||||
* Custom dropdown icon template.
|
||||
* @param {Object} scope - dropdown icon slot's params.
|
||||
*/
|
||||
dropdownicon(scope: {
|
||||
/**
|
||||
* Style class of the dropdown icon
|
||||
*/
|
||||
class: any;
|
||||
}): VNode[];
|
||||
/**
|
||||
* Custom loading icon template.
|
||||
* @param {Object} scope - loading icon slot's params.
|
||||
*/
|
||||
loadingicon(scope: {
|
||||
/**
|
||||
* Style class of the loading icon
|
||||
*/
|
||||
class: any;
|
||||
}): VNode[];
|
||||
/**
|
||||
* Custom filter icon template.
|
||||
* @param {Object} scope - filter icon slot's params.
|
||||
*/
|
||||
filtericon(scope: {
|
||||
/**
|
||||
* Style class of the filter icon
|
||||
*/
|
||||
class: any;
|
||||
}): VNode[];
|
||||
}
|
||||
export interface DropdownSlots extends Select.SelectSlots {}
|
||||
|
||||
/**
|
||||
* Defines valid emits in Dropdown component.
|
||||
*/
|
||||
export interface DropdownEmits {
|
||||
/**
|
||||
* Emitted when the value changes.
|
||||
* @param {*} value - New value.
|
||||
*/
|
||||
'update:modelValue'(value: any): void;
|
||||
/**
|
||||
* Callback to invoke on value change.
|
||||
* @param {DropdownChangeEvent} event - Custom change event.
|
||||
*/
|
||||
change(event: DropdownChangeEvent): void;
|
||||
/**
|
||||
* Callback to invoke when the component receives focus.
|
||||
* @param {Event} event - Browser event.
|
||||
*/
|
||||
focus(event: Event): void;
|
||||
/**
|
||||
* Callback to invoke when the component loses focus.
|
||||
* @param {Event} event - Browser event.
|
||||
*/
|
||||
blur(event: Event): void;
|
||||
/**
|
||||
* Callback to invoke before the overlay is shown.
|
||||
*/
|
||||
'before-show'(): void;
|
||||
/**
|
||||
* Callback to invoke before the overlay is hidden.
|
||||
*/
|
||||
'before-hide'(): void;
|
||||
/**
|
||||
* Callback to invoke when the overlay is shown.
|
||||
*/
|
||||
show(): void;
|
||||
/**
|
||||
* Callback to invoke when the overlay is hidden.
|
||||
*/
|
||||
hide(): void;
|
||||
/**
|
||||
* Callback to invoke on filter input.
|
||||
* @param {DropdownFilterEvent} event - Custom filter event.
|
||||
*/
|
||||
filter(event: DropdownFilterEvent): void;
|
||||
}
|
||||
export interface DropdownEmits extends Select.SelectEmits {}
|
||||
|
||||
/**
|
||||
* @deprecated Deprecated since v4. Use Select component instead.
|
||||
*
|
||||
* **PrimeVue - Dropdown**
|
||||
*
|
||||
* _Dropdown also known as Select, is used to choose an item from a collection of options._
|
||||
*
|
||||
* [Live Demo](https://www.primevue.org/dropdown/)
|
||||
* [Live Demo](https://www.primevue.org/select/)
|
||||
* --- ---
|
||||
* ![PrimeVue](https://primefaces.org/cdn/primevue/images/logo-100.png)
|
||||
*
|
||||
|
|
|
@ -1,991 +1,11 @@
|
|||
<template>
|
||||
<div ref="container" :id="id" :class="cx('root')" @click="onContainerClick" v-bind="ptmi('root')">
|
||||
<input
|
||||
v-if="editable"
|
||||
ref="focusInput"
|
||||
:id="inputId"
|
||||
type="text"
|
||||
:class="[cx('input'), inputClass]"
|
||||
:style="inputStyle"
|
||||
:value="editableInputValue"
|
||||
:placeholder="placeholder"
|
||||
:tabindex="!disabled ? tabindex : -1"
|
||||
:disabled="disabled"
|
||||
autocomplete="off"
|
||||
role="combobox"
|
||||
:aria-label="ariaLabel"
|
||||
:aria-labelledby="ariaLabelledby"
|
||||
aria-haspopup="listbox"
|
||||
:aria-expanded="overlayVisible"
|
||||
:aria-controls="id + '_list'"
|
||||
:aria-activedescendant="focused ? focusedOptionId : undefined"
|
||||
:aria-invalid="invalid || undefined"
|
||||
@focus="onFocus"
|
||||
@blur="onBlur"
|
||||
@keydown="onKeyDown"
|
||||
@input="onEditableInput"
|
||||
v-bind="ptm('input')"
|
||||
/>
|
||||
<span
|
||||
v-else
|
||||
ref="focusInput"
|
||||
:id="inputId"
|
||||
:class="[cx('input'), inputClass]"
|
||||
:style="inputStyle"
|
||||
:tabindex="!disabled ? tabindex : -1"
|
||||
role="combobox"
|
||||
:aria-label="ariaLabel || (label === 'p-emptylabel' ? undefined : label)"
|
||||
:aria-labelledby="ariaLabelledby"
|
||||
aria-haspopup="listbox"
|
||||
:aria-expanded="overlayVisible"
|
||||
:aria-controls="id + '_list'"
|
||||
:aria-activedescendant="focused ? focusedOptionId : undefined"
|
||||
:aria-disabled="disabled"
|
||||
@focus="onFocus"
|
||||
@blur="onBlur"
|
||||
@keydown="onKeyDown"
|
||||
v-bind="ptm('input')"
|
||||
>
|
||||
<slot name="value" :value="modelValue" :placeholder="placeholder">{{ label === 'p-emptylabel' ? ' ' : label || 'empty' }}</slot>
|
||||
</span>
|
||||
<slot v-if="showClear && modelValue != null" name="clearicon" :class="cx('clearIcon')" :onClick="onClearClick" :clearCallback="onClearClick">
|
||||
<component :is="clearIcon ? 'i' : 'TimesIcon'" ref="clearIcon" :class="[cx('clearIcon'), clearIcon]" @click="onClearClick" v-bind="ptm('clearIcon')" data-pc-section="clearicon" />
|
||||
</slot>
|
||||
<div :class="cx('trigger')" v-bind="ptm('trigger')">
|
||||
<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')" />
|
||||
</slot>
|
||||
<slot v-else name="dropdownicon" :class="cx('dropdownIcon')">
|
||||
<component :is="dropdownIcon ? 'span' : 'ChevronDownIcon'" :class="[cx('dropdownIcon'), dropdownIcon]" aria-hidden="true" v-bind="ptm('dropdownIcon')" />
|
||||
</slot>
|
||||
</div>
|
||||
<Portal :appendTo="appendTo">
|
||||
<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" :class="[cx('panel'), panelClass]" :style="panelStyle" @click="onOverlayClick" @keydown="onOverlayKeyDown" v-bind="ptm('panel')">
|
||||
<span
|
||||
ref="firstHiddenFocusableElementOnOverlay"
|
||||
role="presentation"
|
||||
aria-hidden="true"
|
||||
class="p-hidden-accessible p-hidden-focusable"
|
||||
:tabindex="0"
|
||||
@focus="onFirstHiddenFocus"
|
||||
v-bind="ptm('hiddenFirstFocusableEl')"
|
||||
:data-p-hidden-accessible="true"
|
||||
:data-p-hidden-focusable="true"
|
||||
></span>
|
||||
<slot name="header" :value="modelValue" :options="visibleOptions"></slot>
|
||||
<div v-if="filter" :class="cx('header')" v-bind="ptm('header')">
|
||||
<div :class="cx('filterContainer')" v-bind="ptm('filterContainer')">
|
||||
<input
|
||||
ref="filterInput"
|
||||
type="text"
|
||||
:value="filterValue"
|
||||
@vue:mounted="onFilterUpdated"
|
||||
@vue:updated="onFilterUpdated"
|
||||
:class="cx('filterInput')"
|
||||
:placeholder="filterPlaceholder"
|
||||
role="searchbox"
|
||||
autocomplete="off"
|
||||
:aria-owns="id + '_list'"
|
||||
:aria-activedescendant="focusedOptionId"
|
||||
@keydown="onFilterKeyDown"
|
||||
@blur="onFilterBlur"
|
||||
@input="onFilterChange"
|
||||
v-bind="ptm('filterInput')"
|
||||
/>
|
||||
<slot name="filtericon" :class="cx('filterIcon')">
|
||||
<component :is="filterIcon ? 'span' : 'SearchIcon'" :class="[cx('filterIcon'), filterIcon]" v-bind="ptm('filterIcon')" />
|
||||
</slot>
|
||||
</div>
|
||||
<span role="status" aria-live="polite" class="p-hidden-accessible" v-bind="ptm('hiddenFilterResult')" :data-p-hidden-accessible="true">
|
||||
{{ filterResultMessageText }}
|
||||
</span>
|
||||
</div>
|
||||
<div :class="cx('wrapper')" :style="{ 'max-height': virtualScrollerDisabled ? scrollHeight : '' }" v-bind="ptm('wrapper')">
|
||||
<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 }">
|
||||
<ul :ref="(el) => listRef(el, contentRef)" :id="id + '_list'" :class="[cx('list'), styleClass]" :style="contentStyle" role="listbox" v-bind="ptm('list')">
|
||||
<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')">
|
||||
<slot name="optiongroup" :option="option.optionGroup" :index="getOptionIndex(i, getItemOptions)">
|
||||
<span :class="cx('itemGroupLabel')" v-bind="ptm('itemGroupLabel')">{{ getOptionGroupLabel(option.optionGroup) }}</span>
|
||||
</slot>
|
||||
</li>
|
||||
<li
|
||||
v-else
|
||||
:id="id + '_' + getOptionIndex(i, getItemOptions)"
|
||||
v-ripple
|
||||
:class="cx('item', { option, focusedOption: getOptionIndex(i, getItemOptions) })"
|
||||
:style="{ height: itemSize ? itemSize + 'px' : undefined }"
|
||||
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)"
|
||||
@mousemove="onOptionMouseMove($event, getOptionIndex(i, getItemOptions))"
|
||||
:data-p-highlight="isSelected(option)"
|
||||
:data-p-focused="focusedOptionIndex === getOptionIndex(i, getItemOptions)"
|
||||
:data-p-disabled="isOptionDisabled(option)"
|
||||
v-bind="getPTItemOptions(option, getItemOptions, i, 'item')"
|
||||
>
|
||||
<template v-if="checkmark">
|
||||
<CheckIcon v-if="isSelected(option)" :class="cx('checkIcon')" v-bind="ptm('checkIcon')" />
|
||||
<BlankIcon v-else :class="cx('blankIcon')" v-bind="ptm('blankIcon')" />
|
||||
</template>
|
||||
<slot name="option" :option="option" :index="getOptionIndex(i, getItemOptions)">
|
||||
<span :class="cx('itemLabel')" v-bind="ptm('itemLabel')">{{ getOptionLabel(option) }}</span>
|
||||
</slot>
|
||||
</li>
|
||||
</template>
|
||||
<li v-if="filterValue && (!items || (items && items.length === 0))" :class="cx('emptyMessage')" role="option" v-bind="ptm('emptyMessage')" :data-p-hidden-accessible="true">
|
||||
<slot name="emptyfilter">{{ emptyFilterMessageText }}</slot>
|
||||
</li>
|
||||
<li v-else-if="!options || (options && options.length === 0)" :class="cx('emptyMessage')" role="option" v-bind="ptm('emptyMessage')" :data-p-hidden-accessible="true">
|
||||
<slot name="empty">{{ emptyMessageText }}</slot>
|
||||
</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>
|
||||
<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">
|
||||
{{ emptyMessageText }}
|
||||
</span>
|
||||
<span role="status" aria-live="polite" class="p-hidden-accessible" v-bind="ptm('hiddenSelectedMessage')" :data-p-hidden-accessible="true">
|
||||
{{ selectedMessageText }}
|
||||
</span>
|
||||
<span
|
||||
ref="lastHiddenFocusableElementOnOverlay"
|
||||
role="presentation"
|
||||
aria-hidden="true"
|
||||
class="p-hidden-accessible p-hidden-focusable"
|
||||
:tabindex="0"
|
||||
@focus="onLastHiddenFocus"
|
||||
v-bind="ptm('hiddenLastFocusableEl')"
|
||||
:data-p-hidden-accessible="true"
|
||||
:data-p-hidden-focusable="true"
|
||||
></span>
|
||||
</div>
|
||||
</transition>
|
||||
</Portal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { FilterService } from 'primevue/api';
|
||||
import BlankIcon from 'primevue/icons/blank';
|
||||
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 OverlayEventBus from 'primevue/overlayeventbus';
|
||||
import Portal from 'primevue/portal';
|
||||
import Ripple from 'primevue/ripple';
|
||||
import { ConnectedOverlayScrollHandler, DomHandler, ObjectUtils, UniqueComponentId, ZIndexUtils } from 'primevue/utils';
|
||||
import VirtualScroller from 'primevue/virtualscroller';
|
||||
import BaseDropdown from './BaseDropdown.vue';
|
||||
import Select from 'primevue/select';
|
||||
|
||||
export default {
|
||||
name: 'Dropdown',
|
||||
extends: BaseDropdown,
|
||||
inheritAttrs: false,
|
||||
emits: ['update:modelValue', 'change', 'focus', 'blur', 'before-show', 'before-hide', 'show', 'hide', 'filter'],
|
||||
outsideClickListener: null,
|
||||
scrollHandler: null,
|
||||
resizeListener: null,
|
||||
labelClickListener: null,
|
||||
overlay: null,
|
||||
list: null,
|
||||
virtualScroller: null,
|
||||
searchTimeout: null,
|
||||
searchValue: null,
|
||||
isModelValueChanged: false,
|
||||
data() {
|
||||
return {
|
||||
id: this.$attrs.id,
|
||||
clicked: false,
|
||||
focused: false,
|
||||
focusedOptionIndex: -1,
|
||||
filterValue: null,
|
||||
overlayVisible: false
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
'$attrs.id': function (newValue) {
|
||||
this.id = newValue || UniqueComponentId();
|
||||
},
|
||||
modelValue() {
|
||||
this.isModelValueChanged = true;
|
||||
},
|
||||
options() {
|
||||
this.autoUpdateModel();
|
||||
}
|
||||
},
|
||||
extends: Select,
|
||||
mounted() {
|
||||
this.id = this.id || UniqueComponentId();
|
||||
this.autoUpdateModel();
|
||||
this.bindLabelClickListener();
|
||||
},
|
||||
updated() {
|
||||
if (this.overlayVisible && this.isModelValueChanged) {
|
||||
this.scrollInView(this.findSelectedOptionIndex());
|
||||
}
|
||||
|
||||
this.isModelValueChanged = false;
|
||||
},
|
||||
beforeUnmount() {
|
||||
this.unbindOutsideClickListener();
|
||||
this.unbindResizeListener();
|
||||
this.unbindLabelClickListener();
|
||||
|
||||
if (this.scrollHandler) {
|
||||
this.scrollHandler.destroy();
|
||||
this.scrollHandler = null;
|
||||
}
|
||||
|
||||
if (this.overlay) {
|
||||
ZIndexUtils.clear(this.overlay);
|
||||
this.overlay = null;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getOptionIndex(index, fn) {
|
||||
return this.virtualScrollerDisabled ? index : fn && fn(index)['index'];
|
||||
},
|
||||
getOptionLabel(option) {
|
||||
return this.optionLabel ? ObjectUtils.resolveFieldData(option, this.optionLabel) : option;
|
||||
},
|
||||
getOptionValue(option) {
|
||||
return this.optionValue ? ObjectUtils.resolveFieldData(option, this.optionValue) : option;
|
||||
},
|
||||
getOptionRenderKey(option, index) {
|
||||
return (this.dataKey ? ObjectUtils.resolveFieldData(option, this.dataKey) : this.getOptionLabel(option)) + '_' + index;
|
||||
},
|
||||
getPTItemOptions(option, itemOptions, index, key) {
|
||||
return this.ptm(key, {
|
||||
context: {
|
||||
option,
|
||||
index,
|
||||
selected: this.isSelected(option),
|
||||
focused: this.focusedOptionIndex === this.getOptionIndex(index, itemOptions),
|
||||
disabled: this.isOptionDisabled(option)
|
||||
}
|
||||
});
|
||||
},
|
||||
isOptionDisabled(option) {
|
||||
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) {
|
||||
return (this.optionGroupLabel ? index - this.visibleOptions.slice(0, index).filter((option) => this.isOptionGroup(option)).length : index) + 1;
|
||||
},
|
||||
show(isFocus) {
|
||||
this.$emit('before-show');
|
||||
this.overlayVisible = true;
|
||||
this.focusedOptionIndex = this.focusedOptionIndex !== -1 ? this.focusedOptionIndex : this.autoOptionFocus ? this.findFirstFocusedOptionIndex() : this.editable ? -1 : this.findSelectedOptionIndex();
|
||||
|
||||
isFocus && DomHandler.focus(this.$refs.focusInput);
|
||||
},
|
||||
hide(isFocus) {
|
||||
const _hide = () => {
|
||||
this.$emit('before-hide');
|
||||
this.overlayVisible = false;
|
||||
this.clicked = false;
|
||||
this.focusedOptionIndex = -1;
|
||||
this.searchValue = '';
|
||||
|
||||
this.resetFilterOnHide && (this.filterValue = null);
|
||||
isFocus && DomHandler.focus(this.$refs.focusInput);
|
||||
};
|
||||
|
||||
setTimeout(() => {
|
||||
_hide();
|
||||
}, 0); // For ScreenReaders
|
||||
},
|
||||
onFocus(event) {
|
||||
if (this.disabled) {
|
||||
// For ScreenReaders
|
||||
return;
|
||||
}
|
||||
|
||||
this.focused = true;
|
||||
|
||||
if (this.overlayVisible) {
|
||||
this.focusedOptionIndex = this.focusedOptionIndex !== -1 ? this.focusedOptionIndex : this.autoOptionFocus ? this.findFirstFocusedOptionIndex() : this.editable ? -1 : this.findSelectedOptionIndex();
|
||||
this.scrollInView(this.focusedOptionIndex);
|
||||
}
|
||||
|
||||
this.$emit('focus', event);
|
||||
},
|
||||
onBlur(event) {
|
||||
this.focused = false;
|
||||
this.focusedOptionIndex = -1;
|
||||
this.searchValue = '';
|
||||
this.$emit('blur', event);
|
||||
},
|
||||
onKeyDown(event) {
|
||||
if (this.disabled || DomHandler.isAndroid()) {
|
||||
event.preventDefault();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const metaKey = event.metaKey || event.ctrlKey;
|
||||
|
||||
switch (event.code) {
|
||||
case 'ArrowDown':
|
||||
this.onArrowDownKey(event);
|
||||
break;
|
||||
|
||||
case 'ArrowUp':
|
||||
this.onArrowUpKey(event, this.editable);
|
||||
break;
|
||||
|
||||
case 'ArrowLeft':
|
||||
case 'ArrowRight':
|
||||
this.onArrowLeftKey(event, this.editable);
|
||||
break;
|
||||
|
||||
case 'Delete':
|
||||
this.onDeleteKey(event);
|
||||
|
||||
case 'Home':
|
||||
this.onHomeKey(event, this.editable);
|
||||
break;
|
||||
|
||||
case 'End':
|
||||
this.onEndKey(event, this.editable);
|
||||
break;
|
||||
|
||||
case 'PageDown':
|
||||
this.onPageDownKey(event);
|
||||
break;
|
||||
|
||||
case 'PageUp':
|
||||
this.onPageUpKey(event);
|
||||
break;
|
||||
|
||||
case 'Space':
|
||||
this.onSpaceKey(event, this.editable);
|
||||
break;
|
||||
|
||||
case 'Enter':
|
||||
case 'NumpadEnter':
|
||||
this.onEnterKey(event);
|
||||
break;
|
||||
|
||||
case 'Escape':
|
||||
this.onEscapeKey(event);
|
||||
break;
|
||||
|
||||
case 'Tab':
|
||||
this.onTabKey(event);
|
||||
break;
|
||||
|
||||
case 'Backspace':
|
||||
this.onBackspaceKey(event, this.editable);
|
||||
break;
|
||||
|
||||
case 'ShiftLeft':
|
||||
case 'ShiftRight':
|
||||
//NOOP
|
||||
break;
|
||||
|
||||
default:
|
||||
if (!metaKey && ObjectUtils.isPrintableCharacter(event.key)) {
|
||||
!this.overlayVisible && this.show();
|
||||
!this.editable && this.searchOptions(event, event.key);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
this.clicked = false;
|
||||
},
|
||||
onEditableInput(event) {
|
||||
const value = event.target.value;
|
||||
|
||||
this.searchValue = '';
|
||||
const matched = this.searchOptions(event, value);
|
||||
|
||||
!matched && (this.focusedOptionIndex = -1);
|
||||
|
||||
this.updateModel(event, value);
|
||||
|
||||
!this.overlayVisible && ObjectUtils.isNotEmpty(value) && this.show();
|
||||
},
|
||||
onContainerClick(event) {
|
||||
if (this.disabled || this.loading) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.target.tagName === 'INPUT' || event.target.getAttribute('data-pc-section') === 'clearicon' || event.target.closest('[data-pc-section="clearicon"]')) {
|
||||
return;
|
||||
} else if (!this.overlay || !this.overlay.contains(event.target)) {
|
||||
this.overlayVisible ? this.hide(true) : this.show(true);
|
||||
}
|
||||
|
||||
this.clicked = true;
|
||||
},
|
||||
onClearClick(event) {
|
||||
this.updateModel(event, null);
|
||||
this.resetFilterOnClear && (this.filterValue = null);
|
||||
},
|
||||
onFirstHiddenFocus(event) {
|
||||
const focusableEl = event.relatedTarget === this.$refs.focusInput ? DomHandler.getFirstFocusableElement(this.overlay, ':not([data-p-hidden-focusable="true"])') : this.$refs.focusInput;
|
||||
|
||||
DomHandler.focus(focusableEl);
|
||||
},
|
||||
onLastHiddenFocus(event) {
|
||||
const focusableEl = event.relatedTarget === this.$refs.focusInput ? DomHandler.getLastFocusableElement(this.overlay, ':not([data-p-hidden-focusable="true"])') : this.$refs.focusInput;
|
||||
|
||||
DomHandler.focus(focusableEl);
|
||||
},
|
||||
onOptionSelect(event, option, isHide = true) {
|
||||
const value = this.getOptionValue(option);
|
||||
|
||||
this.updateModel(event, value);
|
||||
isHide && this.hide(true);
|
||||
},
|
||||
onOptionMouseMove(event, index) {
|
||||
if (this.focusOnHover) {
|
||||
this.changeFocusedOptionIndex(event, index);
|
||||
}
|
||||
},
|
||||
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':
|
||||
case 'NumpadEnter':
|
||||
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;
|
||||
}
|
||||
},
|
||||
onDeleteKey(event) {
|
||||
if (this.showClear) {
|
||||
this.updateModel(event, null);
|
||||
event.preventDefault();
|
||||
}
|
||||
},
|
||||
onArrowDownKey(event) {
|
||||
if (!this.overlayVisible) {
|
||||
this.show();
|
||||
this.editable && this.changeFocusedOptionIndex(event, this.findSelectedOptionIndex());
|
||||
} else {
|
||||
const optionIndex = this.focusedOptionIndex !== -1 ? this.findNextOptionIndex(this.focusedOptionIndex) : this.clicked ? this.findFirstOptionIndex() : this.findFirstFocusedOptionIndex();
|
||||
|
||||
this.changeFocusedOptionIndex(event, optionIndex);
|
||||
}
|
||||
|
||||
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();
|
||||
} else {
|
||||
const optionIndex = this.focusedOptionIndex !== -1 ? this.findPrevOptionIndex(this.focusedOptionIndex) : this.clicked ? this.findLastOptionIndex() : this.findLastFocusedOptionIndex();
|
||||
|
||||
this.changeFocusedOptionIndex(event, optionIndex);
|
||||
|
||||
!this.overlayVisible && this.show();
|
||||
event.preventDefault();
|
||||
}
|
||||
},
|
||||
onArrowLeftKey(event, pressedInInputText = false) {
|
||||
pressedInInputText && (this.focusedOptionIndex = -1);
|
||||
},
|
||||
onHomeKey(event, pressedInInputText = false) {
|
||||
if (pressedInInputText) {
|
||||
event.currentTarget.setSelectionRange(0, 0);
|
||||
this.focusedOptionIndex = -1;
|
||||
} else {
|
||||
this.changeFocusedOptionIndex(event, this.findFirstOptionIndex());
|
||||
|
||||
!this.overlayVisible && this.show();
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
},
|
||||
onEndKey(event, pressedInInputText = false) {
|
||||
if (pressedInInputText) {
|
||||
const target = event.currentTarget;
|
||||
const len = target.value.length;
|
||||
|
||||
target.setSelectionRange(len, len);
|
||||
this.focusedOptionIndex = -1;
|
||||
} else {
|
||||
this.changeFocusedOptionIndex(event, this.findLastOptionIndex());
|
||||
|
||||
!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) {
|
||||
this.focusedOptionIndex = -1; // reset
|
||||
this.onArrowDownKey(event);
|
||||
} else {
|
||||
if (this.focusedOptionIndex !== -1) {
|
||||
this.onOptionSelect(event, this.visibleOptions[this.focusedOptionIndex]);
|
||||
}
|
||||
|
||||
this.hide();
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
},
|
||||
onSpaceKey(event, pressedInInputText = false) {
|
||||
!pressedInInputText && this.onEnterKey(event);
|
||||
},
|
||||
onEscapeKey(event) {
|
||||
this.overlayVisible && this.hide(true);
|
||||
event.preventDefault();
|
||||
event.stopPropagation(); //@todo will be changed next versionss
|
||||
},
|
||||
onTabKey(event, pressedInInputText = false) {
|
||||
if (!pressedInInputText) {
|
||||
if (this.overlayVisible && this.hasFocusableElements()) {
|
||||
DomHandler.focus(this.$refs.firstHiddenFocusableElementOnOverlay);
|
||||
|
||||
event.preventDefault();
|
||||
} else {
|
||||
if (this.focusedOptionIndex !== -1) {
|
||||
this.onOptionSelect(event, this.visibleOptions[this.focusedOptionIndex]);
|
||||
}
|
||||
|
||||
this.overlayVisible && this.hide(this.filter);
|
||||
}
|
||||
}
|
||||
},
|
||||
onBackspaceKey(event, pressedInInputText = false) {
|
||||
if (pressedInInputText) {
|
||||
!this.overlayVisible && this.show();
|
||||
}
|
||||
},
|
||||
onOverlayEnter(el) {
|
||||
ZIndexUtils.set('overlay', el, this.$primevue.config.zIndex.overlay);
|
||||
|
||||
DomHandler.addStyles(el, { position: 'absolute', top: '0', left: '0' });
|
||||
this.alignOverlay();
|
||||
this.scrollInView();
|
||||
|
||||
this.autoFilterFocus && DomHandler.focus(this.$refs.filterInput);
|
||||
},
|
||||
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);
|
||||
} else {
|
||||
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.overlay && !this.$el.contains(event.target) && !this.overlay.contains(event.target)) {
|
||||
this.hide();
|
||||
}
|
||||
};
|
||||
|
||||
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();
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('resize', this.resizeListener);
|
||||
}
|
||||
},
|
||||
unbindResizeListener() {
|
||||
if (this.resizeListener) {
|
||||
window.removeEventListener('resize', this.resizeListener);
|
||||
this.resizeListener = null;
|
||||
}
|
||||
},
|
||||
bindLabelClickListener() {
|
||||
if (!this.editable && !this.labelClickListener) {
|
||||
const label = document.querySelector(`label[for="${this.inputId}"]`);
|
||||
|
||||
if (label && DomHandler.isVisible(label)) {
|
||||
this.labelClickListener = () => {
|
||||
DomHandler.focus(this.$refs.focusInput);
|
||||
};
|
||||
|
||||
label.addEventListener('click', this.labelClickListener);
|
||||
}
|
||||
}
|
||||
},
|
||||
unbindLabelClickListener() {
|
||||
if (this.labelClickListener) {
|
||||
const label = document.querySelector(`label[for="${this.inputId}"]`);
|
||||
|
||||
if (label && DomHandler.isVisible(label)) {
|
||||
label.removeEventListener('click', this.labelClickListener);
|
||||
}
|
||||
}
|
||||
},
|
||||
hasFocusableElements() {
|
||||
return DomHandler.getFocusableElements(this.overlay, ':not([data-p-hidden-focusable="true"])').length > 0;
|
||||
},
|
||||
isOptionMatched(option) {
|
||||
return this.isValidOption(option) && typeof this.getOptionLabel(option) === 'string' && this.getOptionLabel(option)?.toLocaleLowerCase(this.filterLocale).startsWith(this.searchValue.toLocaleLowerCase(this.filterLocale));
|
||||
},
|
||||
isValidOption(option) {
|
||||
return ObjectUtils.isNotEmpty(option) && !(this.isOptionDisabled(option) || this.isOptionGroup(option));
|
||||
},
|
||||
isValidSelectedOption(option) {
|
||||
return this.isValidOption(option) && this.isSelected(option);
|
||||
},
|
||||
isSelected(option) {
|
||||
return this.isValidOption(option) && ObjectUtils.equals(this.modelValue, this.getOptionValue(option), this.equalityKey);
|
||||
},
|
||||
findFirstOptionIndex() {
|
||||
return this.visibleOptions.findIndex((option) => this.isValidOption(option));
|
||||
},
|
||||
findLastOptionIndex() {
|
||||
return ObjectUtils.findLastIndex(this.visibleOptions, (option) => this.isValidOption(option));
|
||||
},
|
||||
findNextOptionIndex(index) {
|
||||
const matchedOptionIndex = index < this.visibleOptions.length - 1 ? this.visibleOptions.slice(index + 1).findIndex((option) => this.isValidOption(option)) : -1;
|
||||
|
||||
return matchedOptionIndex > -1 ? matchedOptionIndex + index + 1 : index;
|
||||
},
|
||||
findPrevOptionIndex(index) {
|
||||
const matchedOptionIndex = index > 0 ? ObjectUtils.findLastIndex(this.visibleOptions.slice(0, index), (option) => this.isValidOption(option)) : -1;
|
||||
|
||||
return matchedOptionIndex > -1 ? matchedOptionIndex : index;
|
||||
},
|
||||
findSelectedOptionIndex() {
|
||||
return this.hasSelectedOption ? this.visibleOptions.findIndex((option) => this.isValidSelectedOption(option)) : -1;
|
||||
},
|
||||
findFirstFocusedOptionIndex() {
|
||||
const selectedIndex = this.findSelectedOptionIndex();
|
||||
|
||||
return selectedIndex < 0 ? this.findFirstOptionIndex() : selectedIndex;
|
||||
},
|
||||
findLastFocusedOptionIndex() {
|
||||
const selectedIndex = this.findSelectedOptionIndex();
|
||||
|
||||
return selectedIndex < 0 ? this.findLastOptionIndex() : selectedIndex;
|
||||
},
|
||||
searchOptions(event, char) {
|
||||
this.searchValue = (this.searchValue || '') + char;
|
||||
|
||||
let optionIndex = -1;
|
||||
let matched = false;
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
if (optionIndex !== -1) {
|
||||
matched = true;
|
||||
}
|
||||
|
||||
if (optionIndex === -1 && this.focusedOptionIndex === -1) {
|
||||
optionIndex = this.findFirstFocusedOptionIndex();
|
||||
}
|
||||
|
||||
if (optionIndex !== -1) {
|
||||
this.changeFocusedOptionIndex(event, optionIndex);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.searchTimeout) {
|
||||
clearTimeout(this.searchTimeout);
|
||||
}
|
||||
|
||||
this.searchTimeout = setTimeout(() => {
|
||||
this.searchValue = '';
|
||||
this.searchTimeout = null;
|
||||
}, 500);
|
||||
|
||||
return matched;
|
||||
},
|
||||
changeFocusedOptionIndex(event, index) {
|
||||
if (this.focusedOptionIndex !== index) {
|
||||
this.focusedOptionIndex = index;
|
||||
this.scrollInView();
|
||||
|
||||
if (this.selectOnFocus) {
|
||||
this.onOptionSelect(event, this.visibleOptions[index], false);
|
||||
}
|
||||
}
|
||||
},
|
||||
scrollInView(index = -1) {
|
||||
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: 'start' });
|
||||
} else if (!this.virtualScrollerDisabled) {
|
||||
this.virtualScroller && this.virtualScroller.scrollToIndex(index !== -1 ? index : this.focusedOptionIndex);
|
||||
}
|
||||
});
|
||||
},
|
||||
autoUpdateModel() {
|
||||
if (this.selectOnFocus && this.autoOptionFocus && !this.hasSelectedOption) {
|
||||
this.focusedOptionIndex = this.findFirstFocusedOptionIndex();
|
||||
this.onOptionSelect(null, this.visibleOptions[this.focusedOptionIndex], false);
|
||||
}
|
||||
},
|
||||
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);
|
||||
|
||||
optionGroupChildren && optionGroupChildren.forEach((o) => result.push(o));
|
||||
|
||||
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() {
|
||||
const options = this.optionGroupLabel ? this.flatOptions(this.options) : this.options || [];
|
||||
|
||||
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) => {
|
||||
const groupChildren = this.getOptionGroupChildren(group);
|
||||
const filteredItems = groupChildren.filter((item) => filteredOptions.includes(item));
|
||||
|
||||
if (filteredItems.length > 0) filtered.push({ ...group, [typeof this.optionGroupChildren === 'string' ? this.optionGroupChildren : 'items']: [...filteredItems] });
|
||||
});
|
||||
|
||||
return this.flatOptions(filtered);
|
||||
}
|
||||
|
||||
return filteredOptions;
|
||||
}
|
||||
|
||||
return options;
|
||||
},
|
||||
hasSelectedOption() {
|
||||
return ObjectUtils.isNotEmpty(this.modelValue);
|
||||
},
|
||||
label() {
|
||||
const selectedOptionIndex = this.findSelectedOptionIndex();
|
||||
|
||||
return selectedOptionIndex !== -1 ? this.getOptionLabel(this.visibleOptions[selectedOptionIndex]) : this.placeholder || 'p-emptylabel';
|
||||
},
|
||||
editableInputValue() {
|
||||
const selectedOptionIndex = this.findSelectedOptionIndex();
|
||||
|
||||
return selectedOptionIndex !== -1 ? this.getOptionLabel(this.visibleOptions[selectedOptionIndex]) : this.modelValue || '';
|
||||
},
|
||||
equalityKey() {
|
||||
return this.optionValue ? null : this.dataKey;
|
||||
},
|
||||
searchFields() {
|
||||
return this.filterFields || [this.optionLabel];
|
||||
},
|
||||
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}', '1') : this.emptySelectionMessageText;
|
||||
},
|
||||
focusedOptionId() {
|
||||
return this.focusedOptionIndex !== -1 ? `${this.id}_${this.focusedOptionIndex}` : null;
|
||||
},
|
||||
ariaSetSize() {
|
||||
return this.visibleOptions.filter((option) => !this.isOptionGroup(option)).length;
|
||||
},
|
||||
virtualScrollerDisabled() {
|
||||
return !this.virtualScrollerOptions;
|
||||
}
|
||||
},
|
||||
directives: {
|
||||
ripple: Ripple
|
||||
},
|
||||
components: {
|
||||
VirtualScroller,
|
||||
Portal,
|
||||
TimesIcon,
|
||||
ChevronDownIcon,
|
||||
SpinnerIcon,
|
||||
SearchIcon,
|
||||
CheckIcon,
|
||||
BlankIcon
|
||||
console.warn('Deprecated since v4. Use Select component instead.');
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
import { BaseStyle } from '../../base/style';
|
||||
import { SelectStyle } from '../../select/style/SelectStyle';
|
||||
|
||||
export interface DropdownStyle extends BaseStyle {}
|
||||
export interface DropdownStyle extends SelectStyle {}
|
||||
|
|
|
@ -1,58 +1,5 @@
|
|||
import BaseStyle from 'primevue/base/style';
|
||||
|
||||
const classes = {
|
||||
root: ({ instance, props, state }) => [
|
||||
'p-select p-component p-inputwrapper',
|
||||
{
|
||||
'p-disabled': props.disabled,
|
||||
'p-invalid': props.invalid,
|
||||
'p-variant-filled': props.variant ? props.variant === 'filled' : instance.$primevue.config.inputStyle === 'filled',
|
||||
'p-focus': state.focused,
|
||||
'p-inputwrapper-filled': instance.hasSelectedOption,
|
||||
'p-inputwrapper-focus': state.focused || state.overlayVisible,
|
||||
'p-select-open': state.overlayVisible
|
||||
}
|
||||
],
|
||||
input: ({ instance, props }) => [
|
||||
'p-select-label',
|
||||
{
|
||||
'p-placeholder': !props.editable && instance.label === props.placeholder,
|
||||
'p-select-label-empty': !props.editable && !instance.$slots['value'] && (instance.label === 'p-emptylabel' || instance.label.length === 0)
|
||||
}
|
||||
],
|
||||
clearIcon: 'p-select-clear-icon',
|
||||
trigger: 'p-select-dropdown',
|
||||
loadingicon: 'p-select-loading-icon',
|
||||
dropdownIcon: 'p-select-dropdown-icon',
|
||||
panel: ({ instance }) => [
|
||||
'p-select-overlay p-component',
|
||||
{
|
||||
'p-ripple-disabled': instance.$primevue.config.ripple === false
|
||||
}
|
||||
],
|
||||
header: 'p-select-header',
|
||||
filterContainer: 'p-select-filter-container',
|
||||
filterInput: 'p-select-filter',
|
||||
filterIcon: 'p-select-filter-icon',
|
||||
wrapper: 'p-select-list-container',
|
||||
list: 'p-select-list',
|
||||
itemGroup: 'p-select-option-group',
|
||||
itemGroupLabel: 'p-select-option-group-label',
|
||||
item: ({ instance, props, state, option, focusedOption }) => [
|
||||
'p-select-option',
|
||||
{
|
||||
'p-select-option-selected': instance.isSelected(option) && props.highlightOnSelect,
|
||||
'p-focus': state.focusedOptionIndex === focusedOption,
|
||||
'p-disabled': instance.isOptionDisabled(option)
|
||||
}
|
||||
],
|
||||
itemLabel: 'p-select-option-label',
|
||||
checkIcon: 'p-select-option-check-icon',
|
||||
blankIcon: 'p-select-option-blank-icon',
|
||||
emptyMessage: 'p-select-empty-message'
|
||||
};
|
||||
|
||||
export default BaseStyle.extend({
|
||||
name: 'dropdown',
|
||||
classes
|
||||
name: 'dropdown'
|
||||
});
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
<script>
|
||||
import BaseComponent from 'primevue/basecomponent';
|
||||
import DropdownStyle from 'primevue/dropdown/style';
|
||||
import SelectStyle from 'primevue/select/style';
|
||||
|
||||
export default {
|
||||
name: 'BaseDropdown',
|
||||
name: 'BaseSelect',
|
||||
extends: BaseComponent,
|
||||
props: {
|
||||
modelValue: null,
|
||||
|
@ -163,7 +163,7 @@ export default {
|
|||
default: null
|
||||
}
|
||||
},
|
||||
style: DropdownStyle,
|
||||
style: SelectStyle,
|
||||
provide() {
|
||||
return {
|
||||
$parentInstance: this
|
|
@ -0,0 +1,762 @@
|
|||
/**
|
||||
*
|
||||
* Select also known as Select, is used to choose an item from a collection of options.
|
||||
*
|
||||
* [Live Demo](https://www.primevue.org/select/)
|
||||
*
|
||||
* @module select
|
||||
*
|
||||
*/
|
||||
import { TransitionProps, VNode } from 'vue';
|
||||
import { ComponentHooks } from '../basecomponent';
|
||||
import { PassThroughOptions } from '../passthrough';
|
||||
import { ClassComponent, DesignToken, GlobalComponentConstructor, HintedString, PassThrough } from '../ts-helpers';
|
||||
import { VirtualScrollerItemOptions, VirtualScrollerPassThroughOptionType, VirtualScrollerProps } from '../virtualscroller';
|
||||
|
||||
export declare type SelectPassThroughOptionType<T = any> = SelectPassThroughAttributes | ((options: SelectPassThroughMethodOptions<T>) => SelectPassThroughAttributes | string) | string | null | undefined;
|
||||
|
||||
export declare type SelectPassThroughTransitionType<T = any> = TransitionProps | ((options: SelectPassThroughMethodOptions<T>) => TransitionProps) | undefined;
|
||||
|
||||
/**
|
||||
* Custom passthrough(pt) option method.
|
||||
*/
|
||||
export interface SelectPassThroughMethodOptions<T> {
|
||||
/**
|
||||
* Defines instance.
|
||||
*/
|
||||
instance: any;
|
||||
/**
|
||||
* Defines valid properties.
|
||||
*/
|
||||
props: SelectProps;
|
||||
/**
|
||||
* Defines current inline state.
|
||||
*/
|
||||
state: SelectState;
|
||||
/**
|
||||
* Defines parent instance.
|
||||
*/
|
||||
parent: T | any;
|
||||
/**
|
||||
* Defines current options.
|
||||
*/
|
||||
context: SelectContext;
|
||||
/**
|
||||
* Defines passthrough(pt) options in global config.
|
||||
*/
|
||||
global: object | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom change event.
|
||||
* @see {@link SelectEmits.change}
|
||||
*/
|
||||
export interface SelectChangeEvent {
|
||||
/**
|
||||
* Browser event.
|
||||
*/
|
||||
originalEvent: Event;
|
||||
/**
|
||||
* Selected option value
|
||||
*/
|
||||
value: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom filter event.
|
||||
* @see {@link SelectEmits.filter}
|
||||
*/
|
||||
export interface SelectFilterEvent {
|
||||
/**
|
||||
* Browser event.
|
||||
*/
|
||||
originalEvent: Event;
|
||||
/**
|
||||
* Filter value
|
||||
*/
|
||||
value: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom passthrough(pt) options.
|
||||
* @see {@link SelectProps.pt}
|
||||
*/
|
||||
export interface SelectPassThroughOptions<T = any> {
|
||||
/**
|
||||
* Used to pass attributes to the root's DOM element.
|
||||
*/
|
||||
root?: SelectPassThroughOptionType<T>;
|
||||
/**
|
||||
* Used to pass attributes to the input's DOM element.
|
||||
*/
|
||||
input?: SelectPassThroughOptionType<T>;
|
||||
/**
|
||||
* Used to pass attributes to the clear icon's DOM element.
|
||||
*/
|
||||
clearIcon?: SelectPassThroughOptionType<T>;
|
||||
/**
|
||||
* Used to pass attributes to the trigger' DOM element.
|
||||
*/
|
||||
trigger?: SelectPassThroughOptionType<T>;
|
||||
/**
|
||||
* Used to pass attributes to the loading icon's DOM element.
|
||||
*/
|
||||
loadingIcon?: SelectPassThroughOptionType<T>;
|
||||
/**
|
||||
* Used to pass attributes to the panel's DOM element.
|
||||
*/
|
||||
panel?: SelectPassThroughOptionType<T>;
|
||||
/**
|
||||
* Used to pass attributes to the header's DOM element.
|
||||
*/
|
||||
header?: SelectPassThroughOptionType<T>;
|
||||
/**
|
||||
* Used to pass attributes to the filter container's DOM element.
|
||||
*/
|
||||
filterContainer?: SelectPassThroughOptionType<T>;
|
||||
/**
|
||||
* Used to pass attributes to the filter input's DOM element.
|
||||
*/
|
||||
filterInput?: SelectPassThroughOptionType<T>;
|
||||
/**
|
||||
* Used to pass attributes to the filter icon's DOM element.
|
||||
*/
|
||||
filterIcon?: SelectPassThroughOptionType<T>;
|
||||
/**
|
||||
* Used to pass attributes to the wrapper's DOM element.
|
||||
*/
|
||||
wrapper?: SelectPassThroughOptionType<T>;
|
||||
/**
|
||||
* Used to pass attributes to the VirtualScroller component.
|
||||
* @see {@link VirtualScrollerPassThroughOptionType}
|
||||
*/
|
||||
virtualScroller?: VirtualScrollerPassThroughOptionType;
|
||||
/**
|
||||
* Used to pass attributes to the list's DOM element.
|
||||
*/
|
||||
list?: SelectPassThroughOptionType<T>;
|
||||
/**
|
||||
* Used to pass attributes to the item group's DOM element.
|
||||
*/
|
||||
itemGroup?: SelectPassThroughOptionType<T>;
|
||||
/**
|
||||
* Used to pass attributes to the item group label's DOM element.
|
||||
*/
|
||||
itemGroupLabel?: SelectPassThroughOptionType<T>;
|
||||
/**
|
||||
* Used to pass attributes to the item's DOM element.
|
||||
*/
|
||||
item?: SelectPassThroughOptionType<T>;
|
||||
/**
|
||||
* Used to pass attributes to the item label's DOM element.
|
||||
*/
|
||||
itemLabel?: SelectPassThroughOptionType<T>;
|
||||
/**
|
||||
* Used to pass attributes to the check icon's DOM element.
|
||||
*/
|
||||
checkIcon?: SelectPassThroughOptionType<T>;
|
||||
/**
|
||||
* Used to pass attributes to the bank icon's DOM element.
|
||||
*/
|
||||
blankIcon?: SelectPassThroughOptionType<T>;
|
||||
/**
|
||||
* Used to pass attributes to the empty message's DOM element.
|
||||
*/
|
||||
emptyMessage?: SelectPassThroughOptionType<T>;
|
||||
/**
|
||||
* Used to pass attributes to the hidden first focusable element's DOM element.
|
||||
*/
|
||||
hiddenFirstFocusableEl?: SelectPassThroughOptionType<T>;
|
||||
/**
|
||||
* Used to pass attributes to the hidden filter result's DOM element.
|
||||
*/
|
||||
hiddenFilterResult?: SelectPassThroughOptionType<T>;
|
||||
/**
|
||||
* Used to pass attributes to the hidden empty message's DOM element.
|
||||
*/
|
||||
hiddenEmptyMessage?: SelectPassThroughOptionType<T>;
|
||||
/**
|
||||
* Used to pass attributes to the hidden selected message's DOM element.
|
||||
*/
|
||||
hiddenSelectedMessage?: SelectPassThroughOptionType<T>;
|
||||
/**
|
||||
* Used to pass attributes to the hidden last focusable element's DOM element.
|
||||
*/
|
||||
hiddenLastFocusableEl?: SelectPassThroughOptionType<T>;
|
||||
/**
|
||||
* Used to manage all lifecycle hooks.
|
||||
* @see {@link BaseComponent.ComponentHooks}
|
||||
*/
|
||||
hooks?: ComponentHooks;
|
||||
/**
|
||||
* Used to control Vue Transition API.
|
||||
*/
|
||||
transition?: SelectPassThroughTransitionType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom passthrough attributes for each DOM elements
|
||||
*/
|
||||
export interface SelectPassThroughAttributes {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines current inline state in Select component.
|
||||
*/
|
||||
export interface SelectState {
|
||||
/**
|
||||
* Current id state as a string.
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* Current focused state as a boolean.
|
||||
* @defaultValue false
|
||||
*/
|
||||
focused: boolean;
|
||||
/**
|
||||
* Current focused item index as a number.
|
||||
* @defaultValue -1
|
||||
*/
|
||||
focusedOptionIndex: number;
|
||||
/**
|
||||
* Current filter value state as a string.
|
||||
*/
|
||||
filterValue: string;
|
||||
/**
|
||||
* Current overlay visible state as a boolean.
|
||||
* @defaultValue false
|
||||
*/
|
||||
overlayVisible: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines current options in Select component.
|
||||
*/
|
||||
export interface SelectContext {
|
||||
/**
|
||||
* Current item option.
|
||||
*/
|
||||
option: any;
|
||||
/**
|
||||
* Current item index.
|
||||
*/
|
||||
index: number;
|
||||
/**
|
||||
* Current selection state of the item as a boolean.
|
||||
* @defaultValue false
|
||||
*/
|
||||
selected: boolean;
|
||||
/**
|
||||
* Current focus state of the item as a boolean.
|
||||
* @defaultValue false
|
||||
*/
|
||||
focused: boolean;
|
||||
/**
|
||||
* Current disabled state of the item as a boolean.
|
||||
* @defaultValue false
|
||||
*/
|
||||
disabled: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines valid properties in Select component.
|
||||
*/
|
||||
export interface SelectProps {
|
||||
/**
|
||||
* Value of the component.
|
||||
*/
|
||||
modelValue?: any;
|
||||
/**
|
||||
* An array of select items to display as the available options.
|
||||
*/
|
||||
options?: any[];
|
||||
/**
|
||||
* Property name or getter function to use as the label of an option.
|
||||
*/
|
||||
optionLabel?: string | ((data: any) => string) | undefined;
|
||||
/**
|
||||
* Property name or getter function to use as the value of an option, defaults to the option itself when not defined.
|
||||
*/
|
||||
optionValue?: string | ((data: any) => any) | undefined;
|
||||
/**
|
||||
* Property name or getter function to use as the disabled flag of an option, defaults to false when not defined.
|
||||
*/
|
||||
optionDisabled?: string | ((data: any) => boolean) | undefined;
|
||||
/**
|
||||
* Property name or getter function to use as the label of an option group.
|
||||
*/
|
||||
optionGroupLabel?: string | ((data: any) => string) | undefined;
|
||||
/**
|
||||
* Property name or getter function that refers to the children options of option group.
|
||||
*/
|
||||
optionGroupChildren?: string | ((data: any) => any[]) | undefined;
|
||||
/**
|
||||
* Height of the viewport, a scrollbar is defined if height of list exceeds this value.
|
||||
* @defaultValue 14rem
|
||||
*/
|
||||
scrollHeight?: string | undefined;
|
||||
/**
|
||||
* When specified, displays a filter input at header.
|
||||
* @defaultValue false
|
||||
*/
|
||||
filter?: boolean | undefined;
|
||||
/**
|
||||
* Placeholder text to show when filter input is empty.
|
||||
*/
|
||||
filterPlaceholder?: string | undefined;
|
||||
/**
|
||||
* Locale to use in filtering. The default locale is the host environment's current locale.
|
||||
*/
|
||||
filterLocale?: string | undefined;
|
||||
/**
|
||||
* Defines the filtering algorithm to use when searching the options.
|
||||
* @defaultValue contains
|
||||
*/
|
||||
filterMatchMode?: HintedString<'contains' | 'startsWith' | 'endsWith'> | undefined;
|
||||
/**
|
||||
* Fields used when filtering the options, defaults to optionLabel.
|
||||
*/
|
||||
filterFields?: string[] | undefined;
|
||||
/**
|
||||
* When present, custom value instead of predefined options can be entered using the editable input field.
|
||||
* @defaultValue false
|
||||
*/
|
||||
editable?: boolean | undefined;
|
||||
/**
|
||||
* Default text to display when no option is selected.
|
||||
*/
|
||||
placeholder?: string | undefined;
|
||||
/**
|
||||
* When present, it specifies that the component should have invalid state style.
|
||||
* @defaultValue false
|
||||
*/
|
||||
invalid?: boolean | undefined;
|
||||
/**
|
||||
* When present, it specifies that the component should be disabled.
|
||||
* @defaultValue false
|
||||
*/
|
||||
disabled?: boolean | undefined;
|
||||
/**
|
||||
* Specifies the input variant of the component.
|
||||
* @defaultValue outlined
|
||||
*/
|
||||
variant?: 'outlined' | 'filled' | undefined;
|
||||
/**
|
||||
* A property to uniquely identify an option.
|
||||
*/
|
||||
dataKey?: string | undefined;
|
||||
/**
|
||||
* When enabled, a clear icon is displayed to clear the value.
|
||||
* @defaultValue false
|
||||
*/
|
||||
showClear?: boolean | undefined;
|
||||
/**
|
||||
* Identifier of the underlying input element.
|
||||
*/
|
||||
inputId?: string | undefined;
|
||||
/**
|
||||
* Inline style of the input field.
|
||||
*/
|
||||
inputStyle?: object | undefined;
|
||||
/**
|
||||
* Style class of the input field.
|
||||
*/
|
||||
inputClass?: string | object | undefined;
|
||||
/**
|
||||
* Inline style of the overlay panel.
|
||||
*/
|
||||
panelStyle?: object | undefined;
|
||||
/**
|
||||
* Style class of the overlay panel.
|
||||
*/
|
||||
panelClass?: string | object | undefined;
|
||||
/**
|
||||
* A valid query selector or an HTMLElement to specify where the overlay gets attached.
|
||||
* @defaultValue body
|
||||
*/
|
||||
appendTo?: HintedString<'body' | 'self'> | undefined | HTMLElement;
|
||||
/**
|
||||
* Whether the select is in loading state.
|
||||
* @defaultValue false
|
||||
*/
|
||||
loading?: boolean | undefined;
|
||||
/**
|
||||
* Icon to display in clear button.
|
||||
* @deprecated since v3.27.0. Use 'clearicon' slot.
|
||||
*/
|
||||
clearIcon?: string | undefined;
|
||||
/**
|
||||
* Icon to display in the select.
|
||||
* @deprecated since v3.27.0. Use 'dropdownicon' slot.
|
||||
*/
|
||||
dropdownIcon?: string | undefined;
|
||||
/**
|
||||
* Icon to display in filter input.
|
||||
* @deprecated since v3.27.0. Use 'filtericon' slot.
|
||||
*/
|
||||
filterIcon?: string | undefined;
|
||||
/**
|
||||
* Icon to display in loading state.
|
||||
* @deprecated since v3.27.0. Use 'loadingicon' slot.
|
||||
*/
|
||||
loadingIcon?: string | undefined;
|
||||
/**
|
||||
* Clears the filter value when hiding the select.
|
||||
* @defaultValue false
|
||||
*/
|
||||
resetFilterOnHide?: boolean;
|
||||
/**
|
||||
* Clears the filter value when clicking on the clear icon.
|
||||
* @defaultValue false
|
||||
*/
|
||||
resetFilterOnClear?: boolean;
|
||||
/**
|
||||
* Whether to use the virtualScroller feature. The properties of VirtualScroller component can be used like an object in it.
|
||||
*/
|
||||
virtualScrollerOptions?: VirtualScrollerProps;
|
||||
/**
|
||||
* Whether to focus on the first visible or selected element when the overlay panel is shown.
|
||||
* @defaultValue false
|
||||
*/
|
||||
autoOptionFocus?: boolean | undefined;
|
||||
/**
|
||||
* Whether to focus on the filter element when the overlay panel is shown.
|
||||
* @defaultValue false
|
||||
*/
|
||||
autoFilterFocus?: boolean | undefined;
|
||||
/**
|
||||
* When enabled, the focused option is selected.
|
||||
* @defaultValue false
|
||||
*/
|
||||
selectOnFocus?: boolean | undefined;
|
||||
/**
|
||||
* When enabled, the focus is placed on the hovered option.
|
||||
* @defaultValue true
|
||||
*/
|
||||
focusOnHover?: boolean | undefined;
|
||||
/**
|
||||
* Whether the selected option will be add highlight class.
|
||||
* @defaultValue true
|
||||
*/
|
||||
highlightOnSelect?: boolean | undefined;
|
||||
/**
|
||||
* Whether the selected option will be shown with a check mark.
|
||||
* @defaultValue false
|
||||
*/
|
||||
checkmark?: boolean | undefined;
|
||||
/**
|
||||
* Text to be displayed in hidden accessible field when filtering returns any results. Defaults to value from PrimeVue locale configuration.
|
||||
* @defaultValue '{0} results are available'
|
||||
*/
|
||||
filterMessage?: string | undefined;
|
||||
/**
|
||||
* Text to be displayed in hidden accessible field when options are selected. Defaults to value from PrimeVue locale configuration.
|
||||
* @defaultValue '{0} items selected'
|
||||
*/
|
||||
selectionMessage?: string | undefined;
|
||||
/**
|
||||
* Text to be displayed in hidden accessible field when any option is not selected. Defaults to value from PrimeVue locale configuration.
|
||||
* @defaultValue No selected item
|
||||
*/
|
||||
emptySelectionMessage?: string | undefined;
|
||||
/**
|
||||
* Text to display when filtering does not return any results. Defaults to value from PrimeVue locale configuration.
|
||||
* @defaultValue No results found
|
||||
*/
|
||||
emptyFilterMessage?: string | undefined;
|
||||
/**
|
||||
* Text to display when there are no options available. Defaults to value from PrimeVue locale configuration.
|
||||
* @defaultValue No results found
|
||||
*/
|
||||
emptyMessage?: string | undefined;
|
||||
/**
|
||||
* Index of the element in tabbing order.
|
||||
*/
|
||||
tabindex?: number | string | undefined;
|
||||
/**
|
||||
* Defines a string value that labels an interactive element.
|
||||
*/
|
||||
ariaLabel?: string | undefined;
|
||||
/**
|
||||
* Identifier of the underlying input element.
|
||||
*/
|
||||
ariaLabelledby?: string | undefined;
|
||||
/**
|
||||
* It generates scoped CSS variables using design tokens for the component.
|
||||
*/
|
||||
dt?: DesignToken<any>;
|
||||
/**
|
||||
* Used to pass attributes to DOM elements inside the component.
|
||||
* @type {SelectPassThroughOptions}
|
||||
*/
|
||||
pt?: PassThrough<SelectPassThroughOptions>;
|
||||
/**
|
||||
* Used to configure passthrough(pt) options of the component.
|
||||
* @type {PassThroughOptions}
|
||||
*/
|
||||
ptOptions?: PassThroughOptions;
|
||||
/**
|
||||
* When enabled, it removes component related styles in the core.
|
||||
* @defaultValue false
|
||||
*/
|
||||
unstyled?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines valid slots in Select component.
|
||||
*/
|
||||
export interface SelectSlots {
|
||||
/**
|
||||
* Custom value template.
|
||||
* @param {Object} scope - value slot's params.
|
||||
*/
|
||||
value(scope: {
|
||||
/**
|
||||
* Value of the component
|
||||
*/
|
||||
value: any;
|
||||
/**
|
||||
* Placeholder prop value
|
||||
*/
|
||||
placeholder: string;
|
||||
}): VNode[];
|
||||
/**
|
||||
* Custom indicator template.
|
||||
* @deprecated since v3.27.0. Use 'dropdownicon or loadingicon' slots.
|
||||
*/
|
||||
indicator(): VNode[];
|
||||
/**
|
||||
* Custom header template of panel.
|
||||
* @param {Object} scope - header slot's params.
|
||||
*/
|
||||
header(scope: {
|
||||
/**
|
||||
* Value of the component
|
||||
*/
|
||||
value: any;
|
||||
/**
|
||||
* Displayed options
|
||||
*/
|
||||
options: any[];
|
||||
}): VNode[];
|
||||
/**
|
||||
* Custom footer template of panel.
|
||||
* @param {Object} scope - footer slot's params.
|
||||
*/
|
||||
footer(scope: {
|
||||
/**
|
||||
* Value of the component
|
||||
*/
|
||||
value: any;
|
||||
/**
|
||||
* Displayed options
|
||||
*/
|
||||
options: any[];
|
||||
}): VNode[];
|
||||
/**
|
||||
* Custom option template.
|
||||
* @param {Object} scope - option slot's params.
|
||||
*/
|
||||
option(scope: {
|
||||
/**
|
||||
* Option instance
|
||||
*/
|
||||
option: any;
|
||||
/**
|
||||
* Index of the option
|
||||
*/
|
||||
index: number;
|
||||
}): VNode[];
|
||||
/**
|
||||
* Custom option group template.
|
||||
* @param {Object} scope - option group slot's params.
|
||||
*/
|
||||
optiongroup(scope: {
|
||||
/**
|
||||
* Option instance
|
||||
*/
|
||||
option: any;
|
||||
/**
|
||||
* Index of the option
|
||||
*/
|
||||
index: number;
|
||||
}): VNode[];
|
||||
/**
|
||||
* Custom empty filter template.
|
||||
*/
|
||||
emptyfilter(): VNode[];
|
||||
/**
|
||||
* Custom empty template.
|
||||
*/
|
||||
empty(): VNode[];
|
||||
/**
|
||||
* Custom content template.
|
||||
* @param {Object} scope - content slot's params.
|
||||
*/
|
||||
content(scope: {
|
||||
/**
|
||||
* An array of objects to display for virtualscroller
|
||||
*/
|
||||
items: any;
|
||||
/**
|
||||
* Style class of the component
|
||||
*/
|
||||
styleClass: string;
|
||||
/**
|
||||
* Referance of the content
|
||||
* @param {HTMLElement} el - Element of 'ref' property
|
||||
*/
|
||||
contentRef: (el: any) => void;
|
||||
/**
|
||||
* Options of the items
|
||||
* @param {number} index - Rendered index
|
||||
* @return {@link VirtualScrollerItemOptions}
|
||||
*/
|
||||
getItemOptions: (index: number) => VirtualScrollerItemOptions;
|
||||
}): VNode[];
|
||||
/**
|
||||
* Custom loader template.
|
||||
* @param {Object} scope - loader slot's params.
|
||||
*/
|
||||
loader(scope: {
|
||||
/**
|
||||
* Options of the loader items for virtualscroller
|
||||
*/
|
||||
options: any[];
|
||||
}): VNode[];
|
||||
/**
|
||||
* Custom clear icon template.
|
||||
* @param {Object} scope - clear icon slot's params.
|
||||
*/
|
||||
clearicon(scope: {
|
||||
/**
|
||||
* Style class of the clear icon
|
||||
*/
|
||||
class: any;
|
||||
/**
|
||||
* Clear icon click function.
|
||||
* @param {Event} event - Browser event
|
||||
* @deprecated since v3.39.0. Use 'clearCallback' property instead.
|
||||
*/
|
||||
onClick: (event: Event) => void;
|
||||
/**
|
||||
* Clear icon click function.
|
||||
* @param {Event} event - Browser event
|
||||
*/
|
||||
clearCallback: (event: Event) => void;
|
||||
}): VNode[];
|
||||
/**
|
||||
* Custom select icon template.
|
||||
* @param {Object} scope - select icon slot's params.
|
||||
*/
|
||||
dropdownicon(scope: {
|
||||
/**
|
||||
* Style class of the select icon
|
||||
*/
|
||||
class: any;
|
||||
}): VNode[];
|
||||
/**
|
||||
* Custom loading icon template.
|
||||
* @param {Object} scope - loading icon slot's params.
|
||||
*/
|
||||
loadingicon(scope: {
|
||||
/**
|
||||
* Style class of the loading icon
|
||||
*/
|
||||
class: any;
|
||||
}): VNode[];
|
||||
/**
|
||||
* Custom filter icon template.
|
||||
* @param {Object} scope - filter icon slot's params.
|
||||
*/
|
||||
filtericon(scope: {
|
||||
/**
|
||||
* Style class of the filter icon
|
||||
*/
|
||||
class: any;
|
||||
}): VNode[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines valid emits in Select component.
|
||||
*/
|
||||
export interface SelectEmits {
|
||||
/**
|
||||
* Emitted when the value changes.
|
||||
* @param {*} value - New value.
|
||||
*/
|
||||
'update:modelValue'(value: any): void;
|
||||
/**
|
||||
* Callback to invoke on value change.
|
||||
* @param {SelectChangeEvent} event - Custom change event.
|
||||
*/
|
||||
change(event: SelectChangeEvent): void;
|
||||
/**
|
||||
* Callback to invoke when the component receives focus.
|
||||
* @param {Event} event - Browser event.
|
||||
*/
|
||||
focus(event: Event): void;
|
||||
/**
|
||||
* Callback to invoke when the component loses focus.
|
||||
* @param {Event} event - Browser event.
|
||||
*/
|
||||
blur(event: Event): void;
|
||||
/**
|
||||
* Callback to invoke before the overlay is shown.
|
||||
*/
|
||||
'before-show'(): void;
|
||||
/**
|
||||
* Callback to invoke before the overlay is hidden.
|
||||
*/
|
||||
'before-hide'(): void;
|
||||
/**
|
||||
* Callback to invoke when the overlay is shown.
|
||||
*/
|
||||
show(): void;
|
||||
/**
|
||||
* Callback to invoke when the overlay is hidden.
|
||||
*/
|
||||
hide(): void;
|
||||
/**
|
||||
* Callback to invoke on filter input.
|
||||
* @param {SelectFilterEvent} event - Custom filter event.
|
||||
*/
|
||||
filter(event: SelectFilterEvent): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* **PrimeVue - Select**
|
||||
*
|
||||
* _Select is used to choose an item from a collection of options._
|
||||
*
|
||||
* [Live Demo](https://www.primevue.org/select/)
|
||||
* --- ---
|
||||
* ![PrimeVue](https://primefaces.org/cdn/primevue/images/logo-100.png)
|
||||
*
|
||||
* @group Component
|
||||
*/
|
||||
declare class Select extends ClassComponent<SelectProps, SelectSlots, SelectEmits> {
|
||||
/**
|
||||
* Shows the overlay.
|
||||
* @param {boolean} [isFocus] - Decides whether to focus on the component. @defaultValue false.
|
||||
*
|
||||
* @memberof Select
|
||||
*/
|
||||
show: (isFocus?: boolean) => void;
|
||||
/**
|
||||
* Hides the overlay.
|
||||
* @param {boolean} [isFocus] - Decides whether to focus on the component. @defaultValue false.
|
||||
*
|
||||
* @memberof Select
|
||||
*/
|
||||
hide: (isFocus?: boolean) => void;
|
||||
}
|
||||
|
||||
declare module 'vue' {
|
||||
export interface GlobalComponents {
|
||||
Select: GlobalComponentConstructor<Select>;
|
||||
}
|
||||
}
|
||||
|
||||
export default Select;
|
|
@ -1,13 +1,13 @@
|
|||
import { mount } from '@vue/test-utils';
|
||||
import PrimeVue from 'primevue/config';
|
||||
import { h } from 'vue';
|
||||
import Dropdown from './Dropdown.vue';
|
||||
import Select from './Select.vue';
|
||||
|
||||
describe('Dropdown.vue', () => {
|
||||
describe('Select.vue', () => {
|
||||
let wrapper;
|
||||
|
||||
beforeEach(async () => {
|
||||
wrapper = mount(Dropdown, {
|
||||
wrapper = mount(Select, {
|
||||
global: {
|
||||
plugins: [PrimeVue],
|
||||
stubs: {
|
||||
|
@ -19,10 +19,10 @@ describe('Dropdown.vue', () => {
|
|||
await wrapper.trigger('click');
|
||||
});
|
||||
|
||||
it('should Dropdown exist', () => {
|
||||
expect(wrapper.find('.p-dropdown.p-component').exists()).toBe(true);
|
||||
expect(wrapper.find('.p-dropdown-panel').exists()).toBe(true);
|
||||
expect(wrapper.find('.p-dropdown-empty-message').exists()).toBe(true);
|
||||
it('should Select exist', () => {
|
||||
expect(wrapper.find('.p-select.p-component').exists()).toBe(true);
|
||||
expect(wrapper.find('.p-select-panel').exists()).toBe(true);
|
||||
expect(wrapper.find('.p-select-empty-message').exists()).toBe(true);
|
||||
expect(wrapper.find('.p-inputwrapper-filled').exists()).toBe(false);
|
||||
expect(wrapper.find('.p-inputwrapper-focus').exists()).toBe(true);
|
||||
});
|
||||
|
@ -32,7 +32,7 @@ describe('option checks', () => {
|
|||
let wrapper;
|
||||
|
||||
beforeEach(async () => {
|
||||
wrapper = mount(Dropdown, {
|
||||
wrapper = mount(Select, {
|
||||
global: {
|
||||
plugins: [PrimeVue],
|
||||
stubs: {
|
||||
|
@ -57,11 +57,11 @@ describe('option checks', () => {
|
|||
});
|
||||
|
||||
it('should show the options', () => {
|
||||
expect(wrapper.find('.p-dropdown-label.p-placeholder').text()).toBe('Select a City');
|
||||
expect(wrapper.find('.p-dropdown-items-wrapper > .p-dropdown-items').exists()).toBe(true);
|
||||
expect(wrapper.find('.p-dropdown-item').exists()).toBe(true);
|
||||
expect(wrapper.findAll('.p-dropdown-item').length).toBe(5);
|
||||
expect(wrapper.findAll('.p-dropdown-item')[0].text()).toBe('New York');
|
||||
expect(wrapper.find('.p-select-label.p-placeholder').text()).toBe('Select a City');
|
||||
expect(wrapper.find('.p-select-items-wrapper > .p-select-items').exists()).toBe(true);
|
||||
expect(wrapper.find('.p-select-item').exists()).toBe(true);
|
||||
expect(wrapper.findAll('.p-select-item').length).toBe(5);
|
||||
expect(wrapper.findAll('.p-select-item')[0].text()).toBe('New York');
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -69,7 +69,7 @@ describe('clear checks', () => {
|
|||
let wrapper;
|
||||
|
||||
beforeEach(async () => {
|
||||
wrapper = mount(Dropdown, {
|
||||
wrapper = mount(Select, {
|
||||
global: {
|
||||
plugins: [PrimeVue],
|
||||
stubs: {
|
||||
|
@ -87,13 +87,13 @@ describe('clear checks', () => {
|
|||
});
|
||||
|
||||
it('should have correct icon', () => {
|
||||
expect(wrapper.find('.p-dropdown-clear-icon').classes()).toContain('pi-discord');
|
||||
expect(wrapper.find('.p-select-clear-icon').classes()).toContain('pi-discord');
|
||||
});
|
||||
|
||||
it('should clear with delete key', async () => {
|
||||
const updateModelSpy = vi.spyOn(wrapper.vm, 'updateModel');
|
||||
|
||||
await wrapper.find('.p-dropdown-label.p-inputtext').trigger('keydown', { code: 'Delete' });
|
||||
await wrapper.find('.p-select-label.p-inputtext').trigger('keydown', { code: 'Delete' });
|
||||
expect(updateModelSpy).toHaveBeenCalledOnce();
|
||||
expect(updateModelSpy).toHaveBeenCalledWith(expect.any(KeyboardEvent), null);
|
||||
});
|
||||
|
@ -103,7 +103,7 @@ describe('editable checks', () => {
|
|||
let wrapper;
|
||||
|
||||
beforeEach(async () => {
|
||||
wrapper = mount(Dropdown, {
|
||||
wrapper = mount(Select, {
|
||||
global: {
|
||||
plugins: [PrimeVue],
|
||||
stubs: {
|
||||
|
@ -129,8 +129,8 @@ describe('editable checks', () => {
|
|||
});
|
||||
|
||||
it('should show the options', () => {
|
||||
expect(wrapper.find('.p-dropdown-label.p-placeholder').exists()).toBe(false);
|
||||
expect(wrapper.find('.p-dropdown-label.p-inputtext').exists()).toBe(true);
|
||||
expect(wrapper.find('.p-select-label.p-placeholder').exists()).toBe(false);
|
||||
expect(wrapper.find('.p-select-label.p-inputtext').exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -138,7 +138,7 @@ describe('option groups checks', () => {
|
|||
let wrapper;
|
||||
|
||||
beforeEach(async () => {
|
||||
wrapper = mount(Dropdown, {
|
||||
wrapper = mount(Select, {
|
||||
global: {
|
||||
plugins: [PrimeVue],
|
||||
stubs: {
|
||||
|
@ -188,8 +188,8 @@ describe('option groups checks', () => {
|
|||
});
|
||||
|
||||
it('should show the option groups', () => {
|
||||
expect(wrapper.findAll('.p-dropdown-item-group').length).toBe(3);
|
||||
expect(wrapper.findAll('.p-dropdown-item-group')[0].text()).toBe('Germany');
|
||||
expect(wrapper.findAll('.p-select-item-group').length).toBe(3);
|
||||
expect(wrapper.findAll('.p-select-item-group')[0].text()).toBe('Germany');
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -197,7 +197,7 @@ describe('templating checks', () => {
|
|||
let wrapper;
|
||||
|
||||
beforeEach(async () => {
|
||||
wrapper = mount(Dropdown, {
|
||||
wrapper = mount(Select, {
|
||||
global: {
|
||||
plugins: [PrimeVue],
|
||||
stubs: {
|
||||
|
@ -249,7 +249,7 @@ describe('empty templating checks', () => {
|
|||
let wrapper;
|
||||
|
||||
beforeEach(async () => {
|
||||
wrapper = mount(Dropdown, {
|
||||
wrapper = mount(Select, {
|
||||
global: {
|
||||
plugins: [PrimeVue],
|
||||
stubs: {
|
||||
|
@ -270,8 +270,8 @@ describe('empty templating checks', () => {
|
|||
});
|
||||
|
||||
it('should see empty slots', () => {
|
||||
expect(wrapper.find('.p-dropdown-empty-message').exists()).toBe(true);
|
||||
expect(wrapper.find('.p-dropdown-empty-message').text()).toBe('Need options prop');
|
||||
expect(wrapper.find('.p-select-empty-message').exists()).toBe(true);
|
||||
expect(wrapper.find('.p-select-empty-message').text()).toBe('Need options prop');
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -279,7 +279,7 @@ describe('loader checks', () => {
|
|||
let wrapper;
|
||||
|
||||
beforeEach(async () => {
|
||||
wrapper = mount(Dropdown, {
|
||||
wrapper = mount(Select, {
|
||||
global: {
|
||||
plugins: [PrimeVue],
|
||||
stubs: {
|
||||
|
@ -306,11 +306,11 @@ describe('loader checks', () => {
|
|||
});
|
||||
|
||||
it('should show the loader', async () => {
|
||||
expect(wrapper.find('.p-dropdown-trigger-icon').classes()).toContain('pi-discord');
|
||||
expect(wrapper.find('.p-select-trigger-icon').classes()).toContain('pi-discord');
|
||||
|
||||
await wrapper.setProps({ loading: false });
|
||||
|
||||
expect(wrapper.find('.p-dropdown-trigger-icon').classes()).not.toContain('pi-discord');
|
||||
expect(wrapper.find('.p-select-trigger-icon').classes()).not.toContain('pi-discord');
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -318,7 +318,7 @@ describe('filter checks', () => {
|
|||
let wrapper;
|
||||
|
||||
beforeEach(async () => {
|
||||
wrapper = mount(Dropdown, {
|
||||
wrapper = mount(Select, {
|
||||
global: {
|
||||
plugins: [PrimeVue],
|
||||
stubs: {
|
||||
|
@ -348,8 +348,8 @@ describe('filter checks', () => {
|
|||
});
|
||||
|
||||
it('should make filtering', async () => {
|
||||
const filterInput = wrapper.find('.p-dropdown-filter');
|
||||
const filterIcon = wrapper.find('.p-dropdown-filter-icon');
|
||||
const filterInput = wrapper.find('.p-select-filter');
|
||||
const filterIcon = wrapper.find('.p-select-filter-icon');
|
||||
|
||||
expect(filterInput.exists()).toBe(true);
|
||||
expect(filterIcon.classes()).toContain('pi-discord');
|
||||
|
@ -364,6 +364,6 @@ describe('filter checks', () => {
|
|||
|
||||
await wrapper.setData({ filterValue: 'c' });
|
||||
|
||||
expect(wrapper.findAll('.p-dropdown-item').length).toBe(2);
|
||||
expect(wrapper.findAll('.p-select-item').length).toBe(2);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,991 @@
|
|||
<template>
|
||||
<div ref="container" :id="id" :class="cx('root')" @click="onContainerClick" v-bind="ptmi('root')">
|
||||
<input
|
||||
v-if="editable"
|
||||
ref="focusInput"
|
||||
:id="inputId"
|
||||
type="text"
|
||||
:class="[cx('input'), inputClass]"
|
||||
:style="inputStyle"
|
||||
:value="editableInputValue"
|
||||
:placeholder="placeholder"
|
||||
:tabindex="!disabled ? tabindex : -1"
|
||||
:disabled="disabled"
|
||||
autocomplete="off"
|
||||
role="combobox"
|
||||
:aria-label="ariaLabel"
|
||||
:aria-labelledby="ariaLabelledby"
|
||||
aria-haspopup="listbox"
|
||||
:aria-expanded="overlayVisible"
|
||||
:aria-controls="id + '_list'"
|
||||
:aria-activedescendant="focused ? focusedOptionId : undefined"
|
||||
:aria-invalid="invalid || undefined"
|
||||
@focus="onFocus"
|
||||
@blur="onBlur"
|
||||
@keydown="onKeyDown"
|
||||
@input="onEditableInput"
|
||||
v-bind="ptm('input')"
|
||||
/>
|
||||
<span
|
||||
v-else
|
||||
ref="focusInput"
|
||||
:id="inputId"
|
||||
:class="[cx('input'), inputClass]"
|
||||
:style="inputStyle"
|
||||
:tabindex="!disabled ? tabindex : -1"
|
||||
role="combobox"
|
||||
:aria-label="ariaLabel || (label === 'p-emptylabel' ? undefined : label)"
|
||||
:aria-labelledby="ariaLabelledby"
|
||||
aria-haspopup="listbox"
|
||||
:aria-expanded="overlayVisible"
|
||||
:aria-controls="id + '_list'"
|
||||
:aria-activedescendant="focused ? focusedOptionId : undefined"
|
||||
:aria-disabled="disabled"
|
||||
@focus="onFocus"
|
||||
@blur="onBlur"
|
||||
@keydown="onKeyDown"
|
||||
v-bind="ptm('input')"
|
||||
>
|
||||
<slot name="value" :value="modelValue" :placeholder="placeholder">{{ label === 'p-emptylabel' ? ' ' : label || 'empty' }}</slot>
|
||||
</span>
|
||||
<slot v-if="showClear && modelValue != null" name="clearicon" :class="cx('clearIcon')" :onClick="onClearClick" :clearCallback="onClearClick">
|
||||
<component :is="clearIcon ? 'i' : 'TimesIcon'" ref="clearIcon" :class="[cx('clearIcon'), clearIcon]" @click="onClearClick" v-bind="ptm('clearIcon')" data-pc-section="clearicon" />
|
||||
</slot>
|
||||
<div :class="cx('trigger')" v-bind="ptm('trigger')">
|
||||
<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')" />
|
||||
</slot>
|
||||
<slot v-else name="dropdownicon" :class="cx('dropdownIcon')">
|
||||
<component :is="dropdownIcon ? 'span' : 'ChevronDownIcon'" :class="[cx('dropdownIcon'), dropdownIcon]" aria-hidden="true" v-bind="ptm('dropdownIcon')" />
|
||||
</slot>
|
||||
</div>
|
||||
<Portal :appendTo="appendTo">
|
||||
<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" :class="[cx('panel'), panelClass]" :style="panelStyle" @click="onOverlayClick" @keydown="onOverlayKeyDown" v-bind="ptm('panel')">
|
||||
<span
|
||||
ref="firstHiddenFocusableElementOnOverlay"
|
||||
role="presentation"
|
||||
aria-hidden="true"
|
||||
class="p-hidden-accessible p-hidden-focusable"
|
||||
:tabindex="0"
|
||||
@focus="onFirstHiddenFocus"
|
||||
v-bind="ptm('hiddenFirstFocusableEl')"
|
||||
:data-p-hidden-accessible="true"
|
||||
:data-p-hidden-focusable="true"
|
||||
></span>
|
||||
<slot name="header" :value="modelValue" :options="visibleOptions"></slot>
|
||||
<div v-if="filter" :class="cx('header')" v-bind="ptm('header')">
|
||||
<div :class="cx('filterContainer')" v-bind="ptm('filterContainer')">
|
||||
<input
|
||||
ref="filterInput"
|
||||
type="text"
|
||||
:value="filterValue"
|
||||
@vue:mounted="onFilterUpdated"
|
||||
@vue:updated="onFilterUpdated"
|
||||
:class="cx('filterInput')"
|
||||
:placeholder="filterPlaceholder"
|
||||
role="searchbox"
|
||||
autocomplete="off"
|
||||
:aria-owns="id + '_list'"
|
||||
:aria-activedescendant="focusedOptionId"
|
||||
@keydown="onFilterKeyDown"
|
||||
@blur="onFilterBlur"
|
||||
@input="onFilterChange"
|
||||
v-bind="ptm('filterInput')"
|
||||
/>
|
||||
<slot name="filtericon" :class="cx('filterIcon')">
|
||||
<component :is="filterIcon ? 'span' : 'SearchIcon'" :class="[cx('filterIcon'), filterIcon]" v-bind="ptm('filterIcon')" />
|
||||
</slot>
|
||||
</div>
|
||||
<span role="status" aria-live="polite" class="p-hidden-accessible" v-bind="ptm('hiddenFilterResult')" :data-p-hidden-accessible="true">
|
||||
{{ filterResultMessageText }}
|
||||
</span>
|
||||
</div>
|
||||
<div :class="cx('wrapper')" :style="{ 'max-height': virtualScrollerDisabled ? scrollHeight : '' }" v-bind="ptm('wrapper')">
|
||||
<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 }">
|
||||
<ul :ref="(el) => listRef(el, contentRef)" :id="id + '_list'" :class="[cx('list'), styleClass]" :style="contentStyle" role="listbox" v-bind="ptm('list')">
|
||||
<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')">
|
||||
<slot name="optiongroup" :option="option.optionGroup" :index="getOptionIndex(i, getItemOptions)">
|
||||
<span :class="cx('itemGroupLabel')" v-bind="ptm('itemGroupLabel')">{{ getOptionGroupLabel(option.optionGroup) }}</span>
|
||||
</slot>
|
||||
</li>
|
||||
<li
|
||||
v-else
|
||||
:id="id + '_' + getOptionIndex(i, getItemOptions)"
|
||||
v-ripple
|
||||
:class="cx('item', { option, focusedOption: getOptionIndex(i, getItemOptions) })"
|
||||
:style="{ height: itemSize ? itemSize + 'px' : undefined }"
|
||||
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)"
|
||||
@mousemove="onOptionMouseMove($event, getOptionIndex(i, getItemOptions))"
|
||||
:data-p-highlight="isSelected(option)"
|
||||
:data-p-focused="focusedOptionIndex === getOptionIndex(i, getItemOptions)"
|
||||
:data-p-disabled="isOptionDisabled(option)"
|
||||
v-bind="getPTItemOptions(option, getItemOptions, i, 'item')"
|
||||
>
|
||||
<template v-if="checkmark">
|
||||
<CheckIcon v-if="isSelected(option)" :class="cx('checkIcon')" v-bind="ptm('checkIcon')" />
|
||||
<BlankIcon v-else :class="cx('blankIcon')" v-bind="ptm('blankIcon')" />
|
||||
</template>
|
||||
<slot name="option" :option="option" :index="getOptionIndex(i, getItemOptions)">
|
||||
<span :class="cx('itemLabel')" v-bind="ptm('itemLabel')">{{ getOptionLabel(option) }}</span>
|
||||
</slot>
|
||||
</li>
|
||||
</template>
|
||||
<li v-if="filterValue && (!items || (items && items.length === 0))" :class="cx('emptyMessage')" role="option" v-bind="ptm('emptyMessage')" :data-p-hidden-accessible="true">
|
||||
<slot name="emptyfilter">{{ emptyFilterMessageText }}</slot>
|
||||
</li>
|
||||
<li v-else-if="!options || (options && options.length === 0)" :class="cx('emptyMessage')" role="option" v-bind="ptm('emptyMessage')" :data-p-hidden-accessible="true">
|
||||
<slot name="empty">{{ emptyMessageText }}</slot>
|
||||
</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>
|
||||
<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">
|
||||
{{ emptyMessageText }}
|
||||
</span>
|
||||
<span role="status" aria-live="polite" class="p-hidden-accessible" v-bind="ptm('hiddenSelectedMessage')" :data-p-hidden-accessible="true">
|
||||
{{ selectedMessageText }}
|
||||
</span>
|
||||
<span
|
||||
ref="lastHiddenFocusableElementOnOverlay"
|
||||
role="presentation"
|
||||
aria-hidden="true"
|
||||
class="p-hidden-accessible p-hidden-focusable"
|
||||
:tabindex="0"
|
||||
@focus="onLastHiddenFocus"
|
||||
v-bind="ptm('hiddenLastFocusableEl')"
|
||||
:data-p-hidden-accessible="true"
|
||||
:data-p-hidden-focusable="true"
|
||||
></span>
|
||||
</div>
|
||||
</transition>
|
||||
</Portal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { FilterService } from 'primevue/api';
|
||||
import BlankIcon from 'primevue/icons/blank';
|
||||
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 OverlayEventBus from 'primevue/overlayeventbus';
|
||||
import Portal from 'primevue/portal';
|
||||
import Ripple from 'primevue/ripple';
|
||||
import { ConnectedOverlayScrollHandler, DomHandler, ObjectUtils, UniqueComponentId, ZIndexUtils } from 'primevue/utils';
|
||||
import VirtualScroller from 'primevue/virtualscroller';
|
||||
import BaseSelect from './BaseSelect.vue';
|
||||
|
||||
export default {
|
||||
name: 'Select',
|
||||
extends: BaseSelect,
|
||||
inheritAttrs: false,
|
||||
emits: ['update:modelValue', 'change', 'focus', 'blur', 'before-show', 'before-hide', 'show', 'hide', 'filter'],
|
||||
outsideClickListener: null,
|
||||
scrollHandler: null,
|
||||
resizeListener: null,
|
||||
labelClickListener: null,
|
||||
overlay: null,
|
||||
list: null,
|
||||
virtualScroller: null,
|
||||
searchTimeout: null,
|
||||
searchValue: null,
|
||||
isModelValueChanged: false,
|
||||
data() {
|
||||
return {
|
||||
id: this.$attrs.id,
|
||||
clicked: false,
|
||||
focused: false,
|
||||
focusedOptionIndex: -1,
|
||||
filterValue: null,
|
||||
overlayVisible: false
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
'$attrs.id': function (newValue) {
|
||||
this.id = newValue || UniqueComponentId();
|
||||
},
|
||||
modelValue() {
|
||||
this.isModelValueChanged = true;
|
||||
},
|
||||
options() {
|
||||
this.autoUpdateModel();
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.id = this.id || UniqueComponentId();
|
||||
this.autoUpdateModel();
|
||||
this.bindLabelClickListener();
|
||||
},
|
||||
updated() {
|
||||
if (this.overlayVisible && this.isModelValueChanged) {
|
||||
this.scrollInView(this.findSelectedOptionIndex());
|
||||
}
|
||||
|
||||
this.isModelValueChanged = false;
|
||||
},
|
||||
beforeUnmount() {
|
||||
this.unbindOutsideClickListener();
|
||||
this.unbindResizeListener();
|
||||
this.unbindLabelClickListener();
|
||||
|
||||
if (this.scrollHandler) {
|
||||
this.scrollHandler.destroy();
|
||||
this.scrollHandler = null;
|
||||
}
|
||||
|
||||
if (this.overlay) {
|
||||
ZIndexUtils.clear(this.overlay);
|
||||
this.overlay = null;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getOptionIndex(index, fn) {
|
||||
return this.virtualScrollerDisabled ? index : fn && fn(index)['index'];
|
||||
},
|
||||
getOptionLabel(option) {
|
||||
return this.optionLabel ? ObjectUtils.resolveFieldData(option, this.optionLabel) : option;
|
||||
},
|
||||
getOptionValue(option) {
|
||||
return this.optionValue ? ObjectUtils.resolveFieldData(option, this.optionValue) : option;
|
||||
},
|
||||
getOptionRenderKey(option, index) {
|
||||
return (this.dataKey ? ObjectUtils.resolveFieldData(option, this.dataKey) : this.getOptionLabel(option)) + '_' + index;
|
||||
},
|
||||
getPTItemOptions(option, itemOptions, index, key) {
|
||||
return this.ptm(key, {
|
||||
context: {
|
||||
option,
|
||||
index,
|
||||
selected: this.isSelected(option),
|
||||
focused: this.focusedOptionIndex === this.getOptionIndex(index, itemOptions),
|
||||
disabled: this.isOptionDisabled(option)
|
||||
}
|
||||
});
|
||||
},
|
||||
isOptionDisabled(option) {
|
||||
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) {
|
||||
return (this.optionGroupLabel ? index - this.visibleOptions.slice(0, index).filter((option) => this.isOptionGroup(option)).length : index) + 1;
|
||||
},
|
||||
show(isFocus) {
|
||||
this.$emit('before-show');
|
||||
this.overlayVisible = true;
|
||||
this.focusedOptionIndex = this.focusedOptionIndex !== -1 ? this.focusedOptionIndex : this.autoOptionFocus ? this.findFirstFocusedOptionIndex() : this.editable ? -1 : this.findSelectedOptionIndex();
|
||||
|
||||
isFocus && DomHandler.focus(this.$refs.focusInput);
|
||||
},
|
||||
hide(isFocus) {
|
||||
const _hide = () => {
|
||||
this.$emit('before-hide');
|
||||
this.overlayVisible = false;
|
||||
this.clicked = false;
|
||||
this.focusedOptionIndex = -1;
|
||||
this.searchValue = '';
|
||||
|
||||
this.resetFilterOnHide && (this.filterValue = null);
|
||||
isFocus && DomHandler.focus(this.$refs.focusInput);
|
||||
};
|
||||
|
||||
setTimeout(() => {
|
||||
_hide();
|
||||
}, 0); // For ScreenReaders
|
||||
},
|
||||
onFocus(event) {
|
||||
if (this.disabled) {
|
||||
// For ScreenReaders
|
||||
return;
|
||||
}
|
||||
|
||||
this.focused = true;
|
||||
|
||||
if (this.overlayVisible) {
|
||||
this.focusedOptionIndex = this.focusedOptionIndex !== -1 ? this.focusedOptionIndex : this.autoOptionFocus ? this.findFirstFocusedOptionIndex() : this.editable ? -1 : this.findSelectedOptionIndex();
|
||||
this.scrollInView(this.focusedOptionIndex);
|
||||
}
|
||||
|
||||
this.$emit('focus', event);
|
||||
},
|
||||
onBlur(event) {
|
||||
this.focused = false;
|
||||
this.focusedOptionIndex = -1;
|
||||
this.searchValue = '';
|
||||
this.$emit('blur', event);
|
||||
},
|
||||
onKeyDown(event) {
|
||||
if (this.disabled || DomHandler.isAndroid()) {
|
||||
event.preventDefault();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const metaKey = event.metaKey || event.ctrlKey;
|
||||
|
||||
switch (event.code) {
|
||||
case 'ArrowDown':
|
||||
this.onArrowDownKey(event);
|
||||
break;
|
||||
|
||||
case 'ArrowUp':
|
||||
this.onArrowUpKey(event, this.editable);
|
||||
break;
|
||||
|
||||
case 'ArrowLeft':
|
||||
case 'ArrowRight':
|
||||
this.onArrowLeftKey(event, this.editable);
|
||||
break;
|
||||
|
||||
case 'Delete':
|
||||
this.onDeleteKey(event);
|
||||
|
||||
case 'Home':
|
||||
this.onHomeKey(event, this.editable);
|
||||
break;
|
||||
|
||||
case 'End':
|
||||
this.onEndKey(event, this.editable);
|
||||
break;
|
||||
|
||||
case 'PageDown':
|
||||
this.onPageDownKey(event);
|
||||
break;
|
||||
|
||||
case 'PageUp':
|
||||
this.onPageUpKey(event);
|
||||
break;
|
||||
|
||||
case 'Space':
|
||||
this.onSpaceKey(event, this.editable);
|
||||
break;
|
||||
|
||||
case 'Enter':
|
||||
case 'NumpadEnter':
|
||||
this.onEnterKey(event);
|
||||
break;
|
||||
|
||||
case 'Escape':
|
||||
this.onEscapeKey(event);
|
||||
break;
|
||||
|
||||
case 'Tab':
|
||||
this.onTabKey(event);
|
||||
break;
|
||||
|
||||
case 'Backspace':
|
||||
this.onBackspaceKey(event, this.editable);
|
||||
break;
|
||||
|
||||
case 'ShiftLeft':
|
||||
case 'ShiftRight':
|
||||
//NOOP
|
||||
break;
|
||||
|
||||
default:
|
||||
if (!metaKey && ObjectUtils.isPrintableCharacter(event.key)) {
|
||||
!this.overlayVisible && this.show();
|
||||
!this.editable && this.searchOptions(event, event.key);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
this.clicked = false;
|
||||
},
|
||||
onEditableInput(event) {
|
||||
const value = event.target.value;
|
||||
|
||||
this.searchValue = '';
|
||||
const matched = this.searchOptions(event, value);
|
||||
|
||||
!matched && (this.focusedOptionIndex = -1);
|
||||
|
||||
this.updateModel(event, value);
|
||||
|
||||
!this.overlayVisible && ObjectUtils.isNotEmpty(value) && this.show();
|
||||
},
|
||||
onContainerClick(event) {
|
||||
if (this.disabled || this.loading) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.target.tagName === 'INPUT' || event.target.getAttribute('data-pc-section') === 'clearicon' || event.target.closest('[data-pc-section="clearicon"]')) {
|
||||
return;
|
||||
} else if (!this.overlay || !this.overlay.contains(event.target)) {
|
||||
this.overlayVisible ? this.hide(true) : this.show(true);
|
||||
}
|
||||
|
||||
this.clicked = true;
|
||||
},
|
||||
onClearClick(event) {
|
||||
this.updateModel(event, null);
|
||||
this.resetFilterOnClear && (this.filterValue = null);
|
||||
},
|
||||
onFirstHiddenFocus(event) {
|
||||
const focusableEl = event.relatedTarget === this.$refs.focusInput ? DomHandler.getFirstFocusableElement(this.overlay, ':not([data-p-hidden-focusable="true"])') : this.$refs.focusInput;
|
||||
|
||||
DomHandler.focus(focusableEl);
|
||||
},
|
||||
onLastHiddenFocus(event) {
|
||||
const focusableEl = event.relatedTarget === this.$refs.focusInput ? DomHandler.getLastFocusableElement(this.overlay, ':not([data-p-hidden-focusable="true"])') : this.$refs.focusInput;
|
||||
|
||||
DomHandler.focus(focusableEl);
|
||||
},
|
||||
onOptionSelect(event, option, isHide = true) {
|
||||
const value = this.getOptionValue(option);
|
||||
|
||||
this.updateModel(event, value);
|
||||
isHide && this.hide(true);
|
||||
},
|
||||
onOptionMouseMove(event, index) {
|
||||
if (this.focusOnHover) {
|
||||
this.changeFocusedOptionIndex(event, index);
|
||||
}
|
||||
},
|
||||
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':
|
||||
case 'NumpadEnter':
|
||||
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;
|
||||
}
|
||||
},
|
||||
onDeleteKey(event) {
|
||||
if (this.showClear) {
|
||||
this.updateModel(event, null);
|
||||
event.preventDefault();
|
||||
}
|
||||
},
|
||||
onArrowDownKey(event) {
|
||||
if (!this.overlayVisible) {
|
||||
this.show();
|
||||
this.editable && this.changeFocusedOptionIndex(event, this.findSelectedOptionIndex());
|
||||
} else {
|
||||
const optionIndex = this.focusedOptionIndex !== -1 ? this.findNextOptionIndex(this.focusedOptionIndex) : this.clicked ? this.findFirstOptionIndex() : this.findFirstFocusedOptionIndex();
|
||||
|
||||
this.changeFocusedOptionIndex(event, optionIndex);
|
||||
}
|
||||
|
||||
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();
|
||||
} else {
|
||||
const optionIndex = this.focusedOptionIndex !== -1 ? this.findPrevOptionIndex(this.focusedOptionIndex) : this.clicked ? this.findLastOptionIndex() : this.findLastFocusedOptionIndex();
|
||||
|
||||
this.changeFocusedOptionIndex(event, optionIndex);
|
||||
|
||||
!this.overlayVisible && this.show();
|
||||
event.preventDefault();
|
||||
}
|
||||
},
|
||||
onArrowLeftKey(event, pressedInInputText = false) {
|
||||
pressedInInputText && (this.focusedOptionIndex = -1);
|
||||
},
|
||||
onHomeKey(event, pressedInInputText = false) {
|
||||
if (pressedInInputText) {
|
||||
event.currentTarget.setSelectionRange(0, 0);
|
||||
this.focusedOptionIndex = -1;
|
||||
} else {
|
||||
this.changeFocusedOptionIndex(event, this.findFirstOptionIndex());
|
||||
|
||||
!this.overlayVisible && this.show();
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
},
|
||||
onEndKey(event, pressedInInputText = false) {
|
||||
if (pressedInInputText) {
|
||||
const target = event.currentTarget;
|
||||
const len = target.value.length;
|
||||
|
||||
target.setSelectionRange(len, len);
|
||||
this.focusedOptionIndex = -1;
|
||||
} else {
|
||||
this.changeFocusedOptionIndex(event, this.findLastOptionIndex());
|
||||
|
||||
!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) {
|
||||
this.focusedOptionIndex = -1; // reset
|
||||
this.onArrowDownKey(event);
|
||||
} else {
|
||||
if (this.focusedOptionIndex !== -1) {
|
||||
this.onOptionSelect(event, this.visibleOptions[this.focusedOptionIndex]);
|
||||
}
|
||||
|
||||
this.hide();
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
},
|
||||
onSpaceKey(event, pressedInInputText = false) {
|
||||
!pressedInInputText && this.onEnterKey(event);
|
||||
},
|
||||
onEscapeKey(event) {
|
||||
this.overlayVisible && this.hide(true);
|
||||
event.preventDefault();
|
||||
event.stopPropagation(); //@todo will be changed next versionss
|
||||
},
|
||||
onTabKey(event, pressedInInputText = false) {
|
||||
if (!pressedInInputText) {
|
||||
if (this.overlayVisible && this.hasFocusableElements()) {
|
||||
DomHandler.focus(this.$refs.firstHiddenFocusableElementOnOverlay);
|
||||
|
||||
event.preventDefault();
|
||||
} else {
|
||||
if (this.focusedOptionIndex !== -1) {
|
||||
this.onOptionSelect(event, this.visibleOptions[this.focusedOptionIndex]);
|
||||
}
|
||||
|
||||
this.overlayVisible && this.hide(this.filter);
|
||||
}
|
||||
}
|
||||
},
|
||||
onBackspaceKey(event, pressedInInputText = false) {
|
||||
if (pressedInInputText) {
|
||||
!this.overlayVisible && this.show();
|
||||
}
|
||||
},
|
||||
onOverlayEnter(el) {
|
||||
ZIndexUtils.set('overlay', el, this.$primevue.config.zIndex.overlay);
|
||||
|
||||
DomHandler.addStyles(el, { position: 'absolute', top: '0', left: '0' });
|
||||
this.alignOverlay();
|
||||
this.scrollInView();
|
||||
|
||||
this.autoFilterFocus && DomHandler.focus(this.$refs.filterInput);
|
||||
},
|
||||
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);
|
||||
} else {
|
||||
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.overlay && !this.$el.contains(event.target) && !this.overlay.contains(event.target)) {
|
||||
this.hide();
|
||||
}
|
||||
};
|
||||
|
||||
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();
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('resize', this.resizeListener);
|
||||
}
|
||||
},
|
||||
unbindResizeListener() {
|
||||
if (this.resizeListener) {
|
||||
window.removeEventListener('resize', this.resizeListener);
|
||||
this.resizeListener = null;
|
||||
}
|
||||
},
|
||||
bindLabelClickListener() {
|
||||
if (!this.editable && !this.labelClickListener) {
|
||||
const label = document.querySelector(`label[for="${this.inputId}"]`);
|
||||
|
||||
if (label && DomHandler.isVisible(label)) {
|
||||
this.labelClickListener = () => {
|
||||
DomHandler.focus(this.$refs.focusInput);
|
||||
};
|
||||
|
||||
label.addEventListener('click', this.labelClickListener);
|
||||
}
|
||||
}
|
||||
},
|
||||
unbindLabelClickListener() {
|
||||
if (this.labelClickListener) {
|
||||
const label = document.querySelector(`label[for="${this.inputId}"]`);
|
||||
|
||||
if (label && DomHandler.isVisible(label)) {
|
||||
label.removeEventListener('click', this.labelClickListener);
|
||||
}
|
||||
}
|
||||
},
|
||||
hasFocusableElements() {
|
||||
return DomHandler.getFocusableElements(this.overlay, ':not([data-p-hidden-focusable="true"])').length > 0;
|
||||
},
|
||||
isOptionMatched(option) {
|
||||
return this.isValidOption(option) && typeof this.getOptionLabel(option) === 'string' && this.getOptionLabel(option)?.toLocaleLowerCase(this.filterLocale).startsWith(this.searchValue.toLocaleLowerCase(this.filterLocale));
|
||||
},
|
||||
isValidOption(option) {
|
||||
return ObjectUtils.isNotEmpty(option) && !(this.isOptionDisabled(option) || this.isOptionGroup(option));
|
||||
},
|
||||
isValidSelectedOption(option) {
|
||||
return this.isValidOption(option) && this.isSelected(option);
|
||||
},
|
||||
isSelected(option) {
|
||||
return this.isValidOption(option) && ObjectUtils.equals(this.modelValue, this.getOptionValue(option), this.equalityKey);
|
||||
},
|
||||
findFirstOptionIndex() {
|
||||
return this.visibleOptions.findIndex((option) => this.isValidOption(option));
|
||||
},
|
||||
findLastOptionIndex() {
|
||||
return ObjectUtils.findLastIndex(this.visibleOptions, (option) => this.isValidOption(option));
|
||||
},
|
||||
findNextOptionIndex(index) {
|
||||
const matchedOptionIndex = index < this.visibleOptions.length - 1 ? this.visibleOptions.slice(index + 1).findIndex((option) => this.isValidOption(option)) : -1;
|
||||
|
||||
return matchedOptionIndex > -1 ? matchedOptionIndex + index + 1 : index;
|
||||
},
|
||||
findPrevOptionIndex(index) {
|
||||
const matchedOptionIndex = index > 0 ? ObjectUtils.findLastIndex(this.visibleOptions.slice(0, index), (option) => this.isValidOption(option)) : -1;
|
||||
|
||||
return matchedOptionIndex > -1 ? matchedOptionIndex : index;
|
||||
},
|
||||
findSelectedOptionIndex() {
|
||||
return this.hasSelectedOption ? this.visibleOptions.findIndex((option) => this.isValidSelectedOption(option)) : -1;
|
||||
},
|
||||
findFirstFocusedOptionIndex() {
|
||||
const selectedIndex = this.findSelectedOptionIndex();
|
||||
|
||||
return selectedIndex < 0 ? this.findFirstOptionIndex() : selectedIndex;
|
||||
},
|
||||
findLastFocusedOptionIndex() {
|
||||
const selectedIndex = this.findSelectedOptionIndex();
|
||||
|
||||
return selectedIndex < 0 ? this.findLastOptionIndex() : selectedIndex;
|
||||
},
|
||||
searchOptions(event, char) {
|
||||
this.searchValue = (this.searchValue || '') + char;
|
||||
|
||||
let optionIndex = -1;
|
||||
let matched = false;
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
if (optionIndex !== -1) {
|
||||
matched = true;
|
||||
}
|
||||
|
||||
if (optionIndex === -1 && this.focusedOptionIndex === -1) {
|
||||
optionIndex = this.findFirstFocusedOptionIndex();
|
||||
}
|
||||
|
||||
if (optionIndex !== -1) {
|
||||
this.changeFocusedOptionIndex(event, optionIndex);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.searchTimeout) {
|
||||
clearTimeout(this.searchTimeout);
|
||||
}
|
||||
|
||||
this.searchTimeout = setTimeout(() => {
|
||||
this.searchValue = '';
|
||||
this.searchTimeout = null;
|
||||
}, 500);
|
||||
|
||||
return matched;
|
||||
},
|
||||
changeFocusedOptionIndex(event, index) {
|
||||
if (this.focusedOptionIndex !== index) {
|
||||
this.focusedOptionIndex = index;
|
||||
this.scrollInView();
|
||||
|
||||
if (this.selectOnFocus) {
|
||||
this.onOptionSelect(event, this.visibleOptions[index], false);
|
||||
}
|
||||
}
|
||||
},
|
||||
scrollInView(index = -1) {
|
||||
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: 'start' });
|
||||
} else if (!this.virtualScrollerDisabled) {
|
||||
this.virtualScroller && this.virtualScroller.scrollToIndex(index !== -1 ? index : this.focusedOptionIndex);
|
||||
}
|
||||
});
|
||||
},
|
||||
autoUpdateModel() {
|
||||
if (this.selectOnFocus && this.autoOptionFocus && !this.hasSelectedOption) {
|
||||
this.focusedOptionIndex = this.findFirstFocusedOptionIndex();
|
||||
this.onOptionSelect(null, this.visibleOptions[this.focusedOptionIndex], false);
|
||||
}
|
||||
},
|
||||
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);
|
||||
|
||||
optionGroupChildren && optionGroupChildren.forEach((o) => result.push(o));
|
||||
|
||||
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() {
|
||||
const options = this.optionGroupLabel ? this.flatOptions(this.options) : this.options || [];
|
||||
|
||||
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) => {
|
||||
const groupChildren = this.getOptionGroupChildren(group);
|
||||
const filteredItems = groupChildren.filter((item) => filteredOptions.includes(item));
|
||||
|
||||
if (filteredItems.length > 0) filtered.push({ ...group, [typeof this.optionGroupChildren === 'string' ? this.optionGroupChildren : 'items']: [...filteredItems] });
|
||||
});
|
||||
|
||||
return this.flatOptions(filtered);
|
||||
}
|
||||
|
||||
return filteredOptions;
|
||||
}
|
||||
|
||||
return options;
|
||||
},
|
||||
hasSelectedOption() {
|
||||
return ObjectUtils.isNotEmpty(this.modelValue);
|
||||
},
|
||||
label() {
|
||||
const selectedOptionIndex = this.findSelectedOptionIndex();
|
||||
|
||||
return selectedOptionIndex !== -1 ? this.getOptionLabel(this.visibleOptions[selectedOptionIndex]) : this.placeholder || 'p-emptylabel';
|
||||
},
|
||||
editableInputValue() {
|
||||
const selectedOptionIndex = this.findSelectedOptionIndex();
|
||||
|
||||
return selectedOptionIndex !== -1 ? this.getOptionLabel(this.visibleOptions[selectedOptionIndex]) : this.modelValue || '';
|
||||
},
|
||||
equalityKey() {
|
||||
return this.optionValue ? null : this.dataKey;
|
||||
},
|
||||
searchFields() {
|
||||
return this.filterFields || [this.optionLabel];
|
||||
},
|
||||
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}', '1') : this.emptySelectionMessageText;
|
||||
},
|
||||
focusedOptionId() {
|
||||
return this.focusedOptionIndex !== -1 ? `${this.id}_${this.focusedOptionIndex}` : null;
|
||||
},
|
||||
ariaSetSize() {
|
||||
return this.visibleOptions.filter((option) => !this.isOptionGroup(option)).length;
|
||||
},
|
||||
virtualScrollerDisabled() {
|
||||
return !this.virtualScrollerOptions;
|
||||
}
|
||||
},
|
||||
directives: {
|
||||
ripple: Ripple
|
||||
},
|
||||
components: {
|
||||
VirtualScroller,
|
||||
Portal,
|
||||
TimesIcon,
|
||||
ChevronDownIcon,
|
||||
SpinnerIcon,
|
||||
SearchIcon,
|
||||
CheckIcon,
|
||||
BlankIcon
|
||||
}
|
||||
};
|
||||
</script>
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"main": "./select.cjs.js",
|
||||
"module": "./select.esm.js",
|
||||
"unpkg": "./select.min.js",
|
||||
"types": "./Select.d.ts",
|
||||
"browser": {
|
||||
"./sfc": "./Select.vue"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
import { BaseStyle } from '../../base/style';
|
||||
|
||||
export interface SelectStyle extends BaseStyle {}
|
|
@ -0,0 +1,58 @@
|
|||
import BaseStyle from 'primevue/base/style';
|
||||
|
||||
const classes = {
|
||||
root: ({ instance, props, state }) => [
|
||||
'p-select p-component p-inputwrapper',
|
||||
{
|
||||
'p-disabled': props.disabled,
|
||||
'p-invalid': props.invalid,
|
||||
'p-variant-filled': props.variant ? props.variant === 'filled' : instance.$primevue.config.inputStyle === 'filled',
|
||||
'p-focus': state.focused,
|
||||
'p-inputwrapper-filled': instance.hasSelectedOption,
|
||||
'p-inputwrapper-focus': state.focused || state.overlayVisible,
|
||||
'p-select-open': state.overlayVisible
|
||||
}
|
||||
],
|
||||
input: ({ instance, props }) => [
|
||||
'p-select-label',
|
||||
{
|
||||
'p-placeholder': !props.editable && instance.label === props.placeholder,
|
||||
'p-select-label-empty': !props.editable && !instance.$slots['value'] && (instance.label === 'p-emptylabel' || instance.label.length === 0)
|
||||
}
|
||||
],
|
||||
clearIcon: 'p-select-clear-icon',
|
||||
trigger: 'p-select-dropdown',
|
||||
loadingicon: 'p-select-loading-icon',
|
||||
dropdownIcon: 'p-select-dropdown-icon',
|
||||
panel: ({ instance }) => [
|
||||
'p-select-overlay p-component',
|
||||
{
|
||||
'p-ripple-disabled': instance.$primevue.config.ripple === false
|
||||
}
|
||||
],
|
||||
header: 'p-select-header',
|
||||
filterContainer: 'p-select-filter-container',
|
||||
filterInput: 'p-select-filter',
|
||||
filterIcon: 'p-select-filter-icon',
|
||||
wrapper: 'p-select-list-container',
|
||||
list: 'p-select-list',
|
||||
itemGroup: 'p-select-option-group',
|
||||
itemGroupLabel: 'p-select-option-group-label',
|
||||
item: ({ instance, props, state, option, focusedOption }) => [
|
||||
'p-select-option',
|
||||
{
|
||||
'p-select-option-selected': instance.isSelected(option) && props.highlightOnSelect,
|
||||
'p-focus': state.focusedOptionIndex === focusedOption,
|
||||
'p-disabled': instance.isOptionDisabled(option)
|
||||
}
|
||||
],
|
||||
itemLabel: 'p-select-option-label',
|
||||
checkIcon: 'p-select-option-check-icon',
|
||||
blankIcon: 'p-select-option-blank-icon',
|
||||
emptyMessage: 'p-select-empty-message'
|
||||
};
|
||||
|
||||
export default BaseStyle.extend({
|
||||
name: 'select',
|
||||
classes
|
||||
});
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"main": "./selectstyle.cjs.js",
|
||||
"module": "./selectstyle.esm.js",
|
||||
"unpkg": "./selectstyle.min.js",
|
||||
"types": "./SelectStyle.d.ts"
|
||||
}
|
|
@ -5,38 +5,38 @@ export default {
|
|||
cursor: pointer;
|
||||
position: relative;
|
||||
user-select: none;
|
||||
background: ${dt('dropdown.background')};
|
||||
border: 1px solid ${dt('dropdown.border.color')};
|
||||
background: ${dt('select.background')};
|
||||
border: 1px solid ${dt('select.border.color')};
|
||||
transition: background-color ${dt('transition.duration')}, color ${dt('transition.duration')}, border-color ${dt('transition.duration')}, outline-color ${dt('transition.duration')};
|
||||
border-radius: ${dt('rounded.base')};
|
||||
outline-color: transparent;
|
||||
box-shadow: ${dt('dropdown.box.shadow')};
|
||||
box-shadow: ${dt('select.box.shadow')};
|
||||
}
|
||||
|
||||
.p-select:not(.p-disabled):hover {
|
||||
border-color: ${dt('dropdown.hover.border.color')};
|
||||
border-color: ${dt('select.hover.border.color')};
|
||||
}
|
||||
|
||||
.p-select:not(.p-disabled).p-focus {
|
||||
border-color:${dt('dropdown.focus.border.color')};
|
||||
border-color:${dt('select.focus.border.color')};
|
||||
outline: 0 none;
|
||||
}
|
||||
|
||||
.p-select.p-variant-filled {
|
||||
background: ${dt('dropdown.filled.background')};
|
||||
background: ${dt('select.filled.background')};
|
||||
}
|
||||
|
||||
.p-select.p-variant-filled.p-focus {
|
||||
background: ${dt('dropdown.filled.focus.background')};
|
||||
background: ${dt('select.filled.focus.background')};
|
||||
}
|
||||
|
||||
.p-select.p-invalid {
|
||||
border-color: ${dt('dropdown.invalid.border.color')};
|
||||
border-color: ${dt('select.invalid.border.color')};
|
||||
}
|
||||
|
||||
.p-select.p-disabled {
|
||||
opacity: 1;
|
||||
background: ${dt('dropdown.disabled.background')};
|
||||
background: ${dt('select.disabled.background')};
|
||||
}
|
||||
|
||||
.p-select-clear-icon {
|
||||
|
@ -53,7 +53,7 @@ export default {
|
|||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
background: transparent;
|
||||
color: ${dt('dropdown.toggle.color')};
|
||||
color: ${dt('select.toggle.color')};
|
||||
width: 2.5rem;
|
||||
border-top-right-radius: ${dt('rounded.base')};
|
||||
border-bottom-right-radius: ${dt('rounded.base')};
|
||||
|
@ -68,14 +68,14 @@ export default {
|
|||
padding: 0.5rem 0.75rem;
|
||||
text-overflow: ellipsis;
|
||||
cursor: pointer;
|
||||
color: ${dt('dropdown.color')};
|
||||
color: ${dt('select.color')};
|
||||
background: transparent;
|
||||
border: 0 none;
|
||||
outline: 0 none;
|
||||
}
|
||||
|
||||
.p-select-label.p-placeholder {
|
||||
color: ${dt('dropdown.placeholder.color')};
|
||||
color: ${dt('select.placeholder.color')};
|
||||
}
|
||||
|
||||
.p-select:has(.p-select-clear-icon) .p-select-label {
|
||||
|
@ -83,7 +83,7 @@ export default {
|
|||
}
|
||||
|
||||
.p-select.p-disabled .p-select-label {
|
||||
color: ${dt('dropdown.disabled.color')};
|
||||
color: ${dt('select.disabled.color')};
|
||||
}
|
||||
|
||||
.p-select-label-empty {
|
||||
|
@ -103,9 +103,9 @@ input.p-select-label {
|
|||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background: ${dt('dropdown.overlay.background')};
|
||||
color: ${dt('dropdown.overlay.color')};
|
||||
border: 1px solid ${dt('dropdown.overlay.border.color')};
|
||||
background: ${dt('select.overlay.background')};
|
||||
color: ${dt('select.overlay.color')};
|
||||
border: 1px solid ${dt('select.overlay.border.color')};
|
||||
border-radius: ${dt('rounded.base')};
|
||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
@ -129,7 +129,7 @@ input.p-select-label {
|
|||
top: 50%;
|
||||
margin-top: -0.5rem;
|
||||
right: 0.75rem;
|
||||
color: ${dt('dropdown.filter.icon.color')};
|
||||
color: ${dt('select.filter.icon.color')};
|
||||
}
|
||||
|
||||
.p-select-list-container {
|
||||
|
@ -140,8 +140,8 @@ input.p-select-label {
|
|||
cursor: auto;
|
||||
margin: 0;
|
||||
padding: 0.5rem 0.75rem;
|
||||
background: ${dt('dropdown.item.group.background')};
|
||||
color: ${dt('dropdown.item.group.color')};
|
||||
background: ${dt('select.item.group.background')};
|
||||
color: ${dt('select.item.group.color')};
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
|
@ -163,7 +163,7 @@ input.p-select-label {
|
|||
margin: 2px 0;
|
||||
padding: 0.5rem 0.75rem;
|
||||
border: 0 none;
|
||||
color: ${dt('dropdown.item.color')};
|
||||
color: ${dt('select.item.color')};
|
||||
background: transparent;
|
||||
transition: background-color ${dt('transition.duration')}, color ${dt('transition.duration')}, border-color ${dt('transition.duration')}, box-shadow ${dt('transition.duration')}, outline-color ${dt('transition.duration')};
|
||||
border-radius: ${dt('rounded.sm')};
|
||||
|
@ -178,25 +178,25 @@ input.p-select-label {
|
|||
}
|
||||
|
||||
.p-select-option:not(.p-select-option-selected):not(.p-disabled).p-focus {
|
||||
background: ${dt('dropdown.item.focus.background')};
|
||||
color: ${dt('dropdown.item.focus.color')};
|
||||
background: ${dt('select.item.focus.background')};
|
||||
color: ${dt('select.item.focus.color')};
|
||||
}
|
||||
|
||||
.p-select-option.p-select-option-selected {
|
||||
background: ${dt('dropdown.item.selected.background')};
|
||||
color: ${dt('dropdown.item.selected.color')};
|
||||
background: ${dt('select.item.selected.background')};
|
||||
color: ${dt('select.item.selected.color')};
|
||||
}
|
||||
|
||||
.p-select-option.p-select-option-selected.p-focus {
|
||||
background: ${dt('dropdown.item.selected.focus.background')};
|
||||
color: ${dt('dropdown.item.selected.focus.color')};
|
||||
background: ${dt('select.item.selected.focus.background')};
|
||||
color: ${dt('select.item.selected.focus.color')};
|
||||
}
|
||||
|
||||
.p-select-option-check-icon {
|
||||
position: relative;
|
||||
margin-left: -0.375rem;
|
||||
margin-right: 0.375rem;
|
||||
color: ${dt('dropdown.checkmark.color')};
|
||||
color: ${dt('select.checkmark.color')};
|
||||
}
|
||||
|
||||
.p-select-empty-message {
|
Loading…
Reference in New Issue