UNPKG

primeng

Version:

PrimeNG is an open source UI library for Angular featuring a rich set of 80+ components, a theme designer, various theme alternatives such as Material, Bootstrap, Tailwind, premium templates and professional support. In addition, it integrates with PrimeB

1,347 lines (1,343 loc) 140 kB
export * from 'primeng/types/multiselect'; import * as i1 from '@angular/common'; import { CommonModule } from '@angular/common'; import * as i0 from '@angular/core'; import { Injectable, InjectionToken, forwardRef, inject, EventEmitter, booleanAttribute, numberAttribute, Output, Input, ViewEncapsulation, Component, input, signal, computed, effect, ContentChildren, ContentChild, ViewChild, ChangeDetectionStrategy, NgModule } from '@angular/core'; import * as i2 from '@angular/forms'; import { NG_VALUE_ACCESSOR, FormsModule } from '@angular/forms'; import { deepEquals, isNotEmpty, isArray, uuid, equals, focus, findLastIndex, resolveFieldData, isPrintableCharacter, getFirstFocusableElement, getLastFocusableElement, findSingle, getFocusableElements } from '@primeuix/utils'; import * as i3 from 'primeng/api'; import { SharedModule, TranslationKeys, Footer, Header, PrimeTemplate } from 'primeng/api'; import { AutoFocus } from 'primeng/autofocus'; import { BaseComponent, PARENT_INSTANCE } from 'primeng/basecomponent'; import { BaseEditableHolder } from 'primeng/baseeditableholder'; import * as i4 from 'primeng/bind'; import { Bind, BindModule } from 'primeng/bind'; import { Checkbox } from 'primeng/checkbox'; import { Chip } from 'primeng/chip'; import { DomHandler, unblockBodyScroll } from 'primeng/dom'; import { Fluid } from 'primeng/fluid'; import { IconField } from 'primeng/iconfield'; import { CheckIcon, SearchIcon, TimesIcon, ChevronDownIcon } from 'primeng/icons'; import { InputIcon } from 'primeng/inputicon'; import { InputText } from 'primeng/inputtext'; import { Overlay } from 'primeng/overlay'; import { Scroller } from 'primeng/scroller'; import { Tooltip } from 'primeng/tooltip'; import { ObjectUtils } from 'primeng/utils'; import { style as style$1 } from '@primeuix/styles/multiselect'; import { BaseStyle } from 'primeng/base'; const style = /*css*/ ` ${style$1} /* For PrimeNG */ .p-multiselect.ng-invalid.ng-dirty { border-color: dt('multiselect.invalid.border.color'); } p-multiSelect.ng-invalid.ng-dirty .p-multiselect-label.p-placeholder, p-multi-select.ng-invalid.ng-dirty .p-multiselect-label.p-placeholder, p-multiselect.ng-invalid.ng-dirty .p-multiselect-label.p-placeholder { color: dt('multiselect.invalid.placeholder.color'); } `; const inlineStyles = { root: ({ instance }) => ({ position: instance.$appendTo() === 'self' ? 'relative' : undefined }) }; const classes = { root: ({ instance }) => [ 'p-multiselect p-component p-inputwrapper', { 'p-multiselect p-component p-inputwrapper': true, 'p-multiselect-display-chip': instance.display === 'chip', 'p-disabled': instance.$disabled(), 'p-invalid': instance.invalid(), 'p-variant-filled': instance.$variant() === 'filled', 'p-focus': instance.focused, 'p-inputwrapper-filled': instance.$filled(), 'p-inputwrapper-focus': instance.focused || instance.overlayVisible, 'p-multiselect-open': instance.overlayVisible, 'p-multiselect-fluid': instance.hasFluid, 'p-multiselect-sm p-inputfield-sm': instance.size() === 'small', 'p-multiselect-lg p-inputfield-lg': instance.size() === 'large' } ], labelContainer: 'p-multiselect-label-container', label: ({ instance }) => ({ 'p-multiselect-label': true, 'p-placeholder': instance.label() === instance.placeholder(), 'p-multiselect-label-empty': !instance.placeholder() && !instance.defaultLabel && (!instance.modelValue() || instance.modelValue().length === 0) }), chipItem: 'p-multiselect-chip-item', pcChip: 'p-multiselect-chip', chipIcon: 'p-multiselect-chip-icon', dropdown: 'p-multiselect-dropdown', loadingIcon: 'p-multiselect-loading-icon', dropdownIcon: 'p-multiselect-dropdown-icon', overlay: 'p-multiselect-overlay p-component-overlay p-component', header: 'p-multiselect-header', pcFilterContainer: 'p-multiselect-filter-container', pcFilter: 'p-multiselect-filter', listContainer: 'p-multiselect-list-container', list: 'p-multiselect-list', optionGroup: 'p-multiselect-option-group', option: ({ instance }) => ({ 'p-multiselect-option': true, 'p-multiselect-option-selected': instance.selected && instance.highlightOnSelect, 'p-disabled': instance.disabled, 'p-focus': instance.focused }), emptyMessage: 'p-multiselect-empty-message', clearIcon: 'p-multiselect-clear-icon' }; class MultiSelectStyle extends BaseStyle { name = 'multiselect'; style = style; classes = classes; inlineStyles = inlineStyles; static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.5", ngImport: i0, type: MultiSelectStyle, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.5", ngImport: i0, type: MultiSelectStyle }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.5", ngImport: i0, type: MultiSelectStyle, decorators: [{ type: Injectable }] }); /** * * MultiSelect is used to select multiple items from a collection. * * [Live Demo](https://www.primeng.org/multiselect/) * * @module multiselectstyle * */ var MultiSelectClasses; (function (MultiSelectClasses) { /** * Class name of the root element */ MultiSelectClasses["root"] = "p-multiselect"; /** * Class name of the label container element */ MultiSelectClasses["labelContainer"] = "p-multiselect-label-container"; /** * Class name of the label element */ MultiSelectClasses["label"] = "p-multiselect-label"; /** * Class name of the chip item element */ MultiSelectClasses["chipItem"] = "p-multiselect-chip-item"; /** * Class name of the chip element */ MultiSelectClasses["pcChip"] = "p-multiselect-chip"; /** * Class name of the chip icon element */ MultiSelectClasses["chipIcon"] = "p-multiselect-chip-icon"; /** * Class name of the dropdown element */ MultiSelectClasses["dropdown"] = "p-multiselect-dropdown"; /** * Class name of the loading icon element */ MultiSelectClasses["loadingIcon"] = "p-multiselect-loading-icon"; /** * Class name of the dropdown icon element */ MultiSelectClasses["dropdownIcon"] = "p-multiselect-dropdown-icon"; /** * Class name of the overlay element */ MultiSelectClasses["overlay"] = "p-multiselect-overlay"; /** * Class name of the header element */ MultiSelectClasses["header"] = "p-multiselect-header"; /** * Class name of the filter container element */ MultiSelectClasses["pcFilterContainer"] = "p-multiselect-filter-container"; /** * Class name of the filter element */ MultiSelectClasses["pcFilter"] = "p-multiselect-filter"; /** * Class name of the list container element */ MultiSelectClasses["listContainer"] = "p-multiselect-list-container"; /** * Class name of the list element */ MultiSelectClasses["list"] = "p-multiselect-list"; /** * Class name of the option group element */ MultiSelectClasses["optionGroup"] = "p-multiselect-option-group"; /** * Class name of the option element */ MultiSelectClasses["option"] = "p-multiselect-option"; /** * Class name of the empty message element */ MultiSelectClasses["emptyMessage"] = "p-multiselect-empty-message"; /** * Class name of the clear icon */ MultiSelectClasses["clearIcon"] = "p-autocomplete-clear-icon"; })(MultiSelectClasses || (MultiSelectClasses = {})); const MULTISELECT_INSTANCE = new InjectionToken('MULTISELECT_INSTANCE'); const MULTISELECT_ITEM_INSTANCE = new InjectionToken('MULTISELECT_ITEM_INSTANCE'); const MULTISELECT_VALUE_ACCESSOR = { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => MultiSelect), multi: true }; class MultiSelectItem extends BaseComponent { $pcMultiSelectItem = inject(MULTISELECT_ITEM_INSTANCE, { optional: true, skipSelf: true }) ?? undefined; hostName = 'MultiSelect'; getPTOptions(key) { return this.ptm(key, { context: { selected: this.selected, focused: this.focused, disabled: this.disabled } }); } option; selected; label; disabled; itemSize; focused; ariaPosInset; ariaSetSize; variant; template; checkIconTemplate; itemCheckboxIconTemplate; highlightOnSelect; onClick = new EventEmitter(); onMouseEnter = new EventEmitter(); _componentStyle = inject(MultiSelectStyle); onOptionClick(event) { this.onClick.emit({ originalEvent: event, option: this.option, selected: this.selected }); event.stopPropagation(); event.preventDefault(); } onOptionMouseEnter(event) { this.onMouseEnter.emit({ originalEvent: event, option: this.option, selected: this.selected }); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.5", ngImport: i0, type: MultiSelectItem, deps: null, target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "16.1.0", version: "21.0.5", type: MultiSelectItem, isStandalone: true, selector: "li[pMultiSelectItem]", inputs: { option: "option", selected: ["selected", "selected", booleanAttribute], label: "label", disabled: ["disabled", "disabled", booleanAttribute], itemSize: ["itemSize", "itemSize", numberAttribute], focused: ["focused", "focused", booleanAttribute], ariaPosInset: "ariaPosInset", ariaSetSize: "ariaSetSize", variant: "variant", template: "template", checkIconTemplate: "checkIconTemplate", itemCheckboxIconTemplate: "itemCheckboxIconTemplate", highlightOnSelect: ["highlightOnSelect", "highlightOnSelect", booleanAttribute] }, outputs: { onClick: "onClick", onMouseEnter: "onMouseEnter" }, host: { attributes: { "role": "option" }, listeners: { "click": "onOptionClick($event)", "mouseenter": "onOptionMouseEnter($event)" }, properties: { "style.height.px": "itemSize", "attr.aria-label": "label", "attr.aria-setsize": "ariaSetSize", "attr.aria-posinset": "ariaPosInset", "attr.aria-selected": "selected", "attr.data-p-selected": "selected", "attr.data-p-focused": "focused", "attr.data-p-highlight": "selected", "attr.data-p-disabled": "disabled", "attr.aria-checked": "selected", "class": "cx('option')" } }, providers: [MultiSelectStyle], usesInheritance: true, ngImport: i0, template: ` <p-checkbox [ngModel]="selected" [binary]="true" [tabindex]="-1" [variant]="variant" [ariaLabel]="label" [pt]="getPTOptions('pcOptionCheckbox')" [unstyled]="unstyled()"> <ng-container *ngIf="itemCheckboxIconTemplate"> <ng-template #icon let-klass="class"> <ng-template *ngTemplateOutlet="itemCheckboxIconTemplate; context: { checked: selected, class: klass }"></ng-template> </ng-template> </ng-container> </p-checkbox> <span *ngIf="!template">{{ label ?? 'empty' }}</span> <ng-container *ngTemplateOutlet="template; context: { $implicit: option }"></ng-container> `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: Checkbox, selector: "p-checkbox, p-checkBox, p-check-box", inputs: ["hostName", "value", "binary", "ariaLabelledBy", "ariaLabel", "tabindex", "inputId", "inputStyle", "styleClass", "inputClass", "indeterminate", "formControl", "checkboxIcon", "readonly", "autofocus", "trueValue", "falseValue", "variant", "size"], outputs: ["onChange", "onFocus", "onBlur"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: SharedModule }], encapsulation: i0.ViewEncapsulation.None }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.5", ngImport: i0, type: MultiSelectItem, decorators: [{ type: Component, args: [{ selector: 'li[pMultiSelectItem]', standalone: true, imports: [CommonModule, Checkbox, FormsModule, SharedModule], template: ` <p-checkbox [ngModel]="selected" [binary]="true" [tabindex]="-1" [variant]="variant" [ariaLabel]="label" [pt]="getPTOptions('pcOptionCheckbox')" [unstyled]="unstyled()"> <ng-container *ngIf="itemCheckboxIconTemplate"> <ng-template #icon let-klass="class"> <ng-template *ngTemplateOutlet="itemCheckboxIconTemplate; context: { checked: selected, class: klass }"></ng-template> </ng-template> </ng-container> </p-checkbox> <span *ngIf="!template">{{ label ?? 'empty' }}</span> <ng-container *ngTemplateOutlet="template; context: { $implicit: option }"></ng-container> `, encapsulation: ViewEncapsulation.None, providers: [MultiSelectStyle], host: { '[style.height.px]': 'itemSize', '[attr.aria-label]': 'label', role: 'option', '[attr.aria-setsize]': 'ariaSetSize', '[attr.aria-posinset]': 'ariaPosInset', '[attr.aria-selected]': 'selected', '[attr.data-p-selected]': 'selected', '[attr.data-p-focused]': 'focused', '[attr.data-p-highlight]': 'selected', '[attr.data-p-disabled]': 'disabled', '[attr.aria-checked]': 'selected', '(click)': 'onOptionClick($event)', '(mouseenter)': 'onOptionMouseEnter($event)', '[class]': "cx('option')" } }] }], propDecorators: { option: [{ type: Input }], selected: [{ type: Input, args: [{ transform: booleanAttribute }] }], label: [{ type: Input }], disabled: [{ type: Input, args: [{ transform: booleanAttribute }] }], itemSize: [{ type: Input, args: [{ transform: numberAttribute }] }], focused: [{ type: Input, args: [{ transform: booleanAttribute }] }], ariaPosInset: [{ type: Input }], ariaSetSize: [{ type: Input }], variant: [{ type: Input }], template: [{ type: Input }], checkIconTemplate: [{ type: Input }], itemCheckboxIconTemplate: [{ type: Input }], highlightOnSelect: [{ type: Input, args: [{ transform: booleanAttribute }] }], onClick: [{ type: Output }], onMouseEnter: [{ type: Output }] } }); /** * MultiSelect is used to select multiple items from a collection. * @group Components */ class MultiSelect extends BaseEditableHolder { zone; filterService; overlayService; /** * Unique identifier of the component * @group Props */ id; /** * Defines a string that labels the input for accessibility. * @group Props */ ariaLabel; /** * Style class of the element. * @deprecated since v20.0.0, use `class` instead. * @group Props */ styleClass; /** * Inline style of the overlay panel. * @group Props */ panelStyle; /** * Style class of the overlay panel element. * @group Props */ panelStyleClass; /** * Identifier of the focus input to match a label defined for the component. * @group Props */ inputId; /** * When present, it specifies that the component cannot be edited. * @group Props */ readonly; /** * Whether to display options as grouped when nested options are provided. * @group Props */ group; /** * When specified, displays an input field to filter the items on keyup. * @group Props */ filter = true; /** * Defines placeholder of the filter input. * @group Props */ filterPlaceHolder; /** * Locale to use in filtering. The default locale is the host environment's current locale. * @group Props */ filterLocale; /** * Specifies the visibility of the options panel. * @group Props */ overlayVisible = false; /** * Index of the element in tabbing order. * @group Props */ tabindex = 0; /** * A property to uniquely identify a value in options. * @group Props */ dataKey; /** * Establishes relationships between the component and label(s) where its value should be one or more element IDs. * @group Props */ ariaLabelledBy; /** * Whether to show labels of selected item labels or use default label. * @group Props * @defaultValue true */ set displaySelectedLabel(val) { this._displaySelectedLabel = val; } get displaySelectedLabel() { return this._displaySelectedLabel; } /** * Decides how many selected item labels to show at most. * @group Props * @defaultValue 3 */ set maxSelectedLabels(val) { this._maxSelectedLabels = val || 0; } get maxSelectedLabels() { return this._maxSelectedLabels; } /** * Maximum number of selectable items. * @group Props */ selectionLimit; /** * Label to display after exceeding max selected labels e.g. ({0} items selected), defaults "ellipsis" keyword to indicate a text-overflow. * @group Props */ selectedItemsLabel; /** * Whether to show the checkbox at header to toggle all items at once. * @group Props */ showToggleAll = true; /** * Text to display when filtering does not return any results. * @group Props */ emptyFilterMessage = ''; /** * Text to display when there is no data. Defaults to global value in i18n translation configuration. * @group Props */ emptyMessage = ''; /** * Clears the filter value when hiding the dropdown. * @group Props */ resetFilterOnHide = false; /** * Icon class of the dropdown icon. * @group Props */ dropdownIcon; /** * Icon class of the chip icon. * @group Props */ chipIcon; /** * Name of the label field of an option. * @group Props */ optionLabel; /** * Name of the value field of an option. * @group Props */ optionValue; /** * Name of the disabled field of an option. * @group Props */ optionDisabled; /** * Name of the label field of an option group. * @group Props */ optionGroupLabel = 'label'; /** * Name of the options field of an option group. * @group Props */ optionGroupChildren = 'items'; /** * Whether to show the header. * @group Props */ showHeader = true; /** * When filtering is enabled, filterBy decides which field or fields (comma separated) to search against. * @group Props */ filterBy; /** * Height of the viewport in pixels, a scrollbar is defined if height of list exceeds this value. * @group Props */ scrollHeight = '200px'; /** * Defines if data is loaded and interacted with in lazy manner. * @group Props */ lazy = false; /** * Whether the data should be loaded on demand during scroll. * @group Props */ virtualScroll; /** * Whether the multiselect is in loading state. * @group Props */ loading = false; /** * Height of an item in the list for VirtualScrolling. * @group Props */ virtualScrollItemSize; /** * Icon to display in loading state. * @group Props */ loadingIcon; /** * Whether to use the scroller feature. The properties of scroller component can be used like an object in it. * @group Props */ virtualScrollOptions; /** * Whether to use overlay API feature. The properties of overlay API can be used like an object in it. * @group Props */ overlayOptions; /** * Defines a string that labels the filter input. * @group Props */ ariaFilterLabel; /** * Defines how the items are filtered. * @group Props */ filterMatchMode = 'contains'; /** * Advisory information to display in a tooltip on hover. * @group Props */ tooltip = ''; /** * Position of the tooltip. * @group Props */ tooltipPosition = 'right'; /** * Type of CSS position. * @group Props */ tooltipPositionStyle = 'absolute'; /** * Style class of the tooltip. * @group Props */ tooltipStyleClass; /** * Applies focus to the filter element when the overlay is shown. * @group Props */ autofocusFilter = false; /** * Defines how the selected items are displayed. * @group Props */ display = 'comma'; /** * Defines the autocomplete is active. * @group Props */ autocomplete = 'off'; /** * When enabled, a clear icon is displayed to clear the value. * @group Props */ showClear = false; /** * When present, it specifies that the component should automatically get focus on load. * @group Props */ autofocus; /** * Label to display when there are no selections. * @group Props */ set placeholder(val) { this._placeholder.set(val); } get placeholder() { return this._placeholder.asReadonly(); } /** * An array of objects to display as the available options. * @group Props */ get options() { return this._options(); } set options(val) { if (!deepEquals(this._options(), val)) { this._options.set(val || []); } } /** * When specified, filter displays with this value. * @group Props */ get filterValue() { return this._filterValue(); } set filterValue(val) { this._filterValue.set(val); } /** * Whether all data is selected. * @group Props */ get selectAll() { return this._selectAll; } set selectAll(value) { this._selectAll = value; } /** * Indicates whether to focus on options when hovering over them, defaults to optionLabel. * @group Props */ focusOnHover = true; /** * Fields used when filtering the options, defaults to optionLabel. * @group Props */ filterFields; /** * Determines if the option will be selected on focus. * @group Props */ selectOnFocus = false; /** * Whether to focus on the first visible or selected element when the overlay panel is shown. * @group Props */ autoOptionFocus = false; /** * Whether the selected option will be add highlight class. * @group Props */ highlightOnSelect = true; /** * Specifies the size of the component. * @defaultValue undefined * @group Props */ size = input(...(ngDevMode ? [undefined, { debugName: "size" }] : [])); /** * Specifies the input variant of the component. * @defaultValue undefined * @group Props */ variant = input(...(ngDevMode ? [undefined, { debugName: "variant" }] : [])); /** * Spans 100% width of the container when enabled. * @defaultValue undefined * @group Props */ fluid = input(undefined, { ...(ngDevMode ? { debugName: "fluid" } : {}), transform: booleanAttribute }); /** * Target element to attach the overlay, valid values are "body" or a local ng-template variable of another element (note: use binding with brackets for template variables, e.g. [appendTo]="mydiv" for a div element having #mydiv as variable name). * @defaultValue 'self' * @group Props */ appendTo = input(undefined, ...(ngDevMode ? [{ debugName: "appendTo" }] : [])); /** * The motion options. * @group Props */ motionOptions = input(undefined, ...(ngDevMode ? [{ debugName: "motionOptions" }] : [])); /** * Callback to invoke when value changes. * @param {MultiSelectChangeEvent} event - Custom change event. * @group Emits */ onChange = new EventEmitter(); /** * Callback to invoke when data is filtered. * @param {MultiSelectFilterEvent} event - Custom filter event. * @group Emits */ onFilter = new EventEmitter(); /** * Callback to invoke when multiselect receives focus. * @param {MultiSelectFocusEvent} event - Custom focus event. * @group Emits */ onFocus = new EventEmitter(); /** * Callback to invoke when multiselect loses focus. * @param {MultiSelectBlurEvent} event - Custom blur event. * @group Emits */ onBlur = new EventEmitter(); /** * Callback to invoke when component is clicked. * @param {Event} event - Browser event. * @group Emits */ onClick = new EventEmitter(); /** * Callback to invoke when input field is cleared. * @group Emits */ onClear = new EventEmitter(); /** * Callback to invoke when overlay panel becomes visible. * @param {AnimationEvent} event - Animation event. * @group Emits */ onPanelShow = new EventEmitter(); /** * Callback to invoke when overlay panel becomes hidden. * @param {AnimationEvent} event - Animation event. * @group Emits */ onPanelHide = new EventEmitter(); /** * Callback to invoke in lazy mode to load new data. * @param {MultiSelectLazyLoadEvent} event - Lazy load event. * @group Emits */ onLazyLoad = new EventEmitter(); /** * Callback to invoke in lazy mode to load new data. * @param {MultiSelectRemoveEvent} event - Remove event. * @group Emits */ onRemove = new EventEmitter(); /** * Callback to invoke when all data is selected. * @param {MultiSelectSelectAllChangeEvent} event - Custom select event. * @group Emits */ onSelectAllChange = new EventEmitter(); overlayViewChild; filterInputChild; focusInputViewChild; itemsViewChild; scroller; lastHiddenFocusableElementOnOverlay; firstHiddenFocusableElementOnOverlay; headerCheckboxViewChild; footerFacet; headerFacet; _componentStyle = inject(MultiSelectStyle); bindDirectiveInstance = inject(Bind, { self: true }); searchValue; searchTimeout; _selectAll = null; _placeholder = signal(undefined, ...(ngDevMode ? [{ debugName: "_placeholder" }] : [])); _disableTooltip = false; value; _filteredOptions; focus; filtered; /** * Custom item template. * @group Templates */ itemTemplate; /** * Custom group template. * @group Templates */ groupTemplate; /** * Custom loader template. * @group Templates */ loaderTemplate; /** * Custom header template. * @group Templates */ headerTemplate; /** * Custom filter template. * @group Templates */ filterTemplate; /** * Custom footer template. * @group Templates */ footerTemplate; /** * Custom empty filter template. * @group Templates */ emptyFilterTemplate; /** * Custom empty template. * @group Templates */ emptyTemplate; /** * Custom selected items template. * @group Templates */ selectedItemsTemplate; /** * Custom loading icon template. * @group Templates */ loadingIconTemplate; /** * Custom filter icon template. * @group Templates */ filterIconTemplate; /** * Custom remove token icon template. * @group Templates */ removeTokenIconTemplate; /** * Custom chip icon template. * @group Templates */ chipIconTemplate; /** * Custom clear icon template. * @group Templates */ clearIconTemplate; /** * Custom dropdown icon template. * @group Templates */ dropdownIconTemplate; /** * Custom item checkbox icon template. * @group Templates */ itemCheckboxIconTemplate; /** * Custom header checkbox icon template. * @group Templates */ headerCheckboxIconTemplate; templates; _itemTemplate; _groupTemplate; _loaderTemplate; _headerTemplate; _filterTemplate; _footerTemplate; _emptyFilterTemplate; _emptyTemplate; _selectedItemsTemplate; _loadingIconTemplate; _filterIconTemplate; _removeTokenIconTemplate; _chipIconTemplate; _clearIconTemplate; _dropdownIconTemplate; _itemCheckboxIconTemplate; _headerCheckboxIconTemplate; $variant = computed(() => this.variant() || this.config.inputStyle() || this.config.inputVariant(), ...(ngDevMode ? [{ debugName: "$variant" }] : [])); $appendTo = computed(() => this.appendTo() || this.config.overlayAppendTo(), ...(ngDevMode ? [{ debugName: "$appendTo" }] : [])); $pcMultiSelect = inject(MULTISELECT_INSTANCE, { optional: true, skipSelf: true }) ?? undefined; pcFluid = inject(Fluid, { optional: true, host: true, skipSelf: true }); get hasFluid() { return this.fluid() ?? !!this.pcFluid; } onAfterContentInit() { this.templates.forEach((item) => { switch (item.getType()) { case 'item': this._itemTemplate = item.template; break; case 'group': this._groupTemplate = item.template; break; case 'selectedItems': case 'selecteditems': this._selectedItemsTemplate = item.template; break; case 'header': this._headerTemplate = item.template; break; case 'filter': this._filterTemplate = item.template; break; case 'emptyfilter': this._emptyFilterTemplate = item.template; break; case 'empty': this._emptyTemplate = item.template; break; case 'footer': this._footerTemplate = item.template; break; case 'loader': this._loaderTemplate = item.template; break; case 'headercheckboxicon': this._headerCheckboxIconTemplate = item.template; break; case 'loadingicon': this._loadingIconTemplate = item.template; break; case 'filtericon': this._filterIconTemplate = item.template; break; case 'removetokenicon': this._removeTokenIconTemplate = item.template; break; case 'clearicon': this._clearIconTemplate = item.template; break; case 'dropdownicon': this._dropdownIconTemplate = item.template; break; case 'itemcheckboxicon': this._itemCheckboxIconTemplate = item.template; break; case 'chipicon': this._chipIconTemplate = item.template; break; default: this._itemTemplate = item.template; break; } }); } headerCheckboxFocus; filterOptions; preventModelTouched; focused = false; itemsWrapper; _displaySelectedLabel = true; _maxSelectedLabels = 3; modelValue = signal(null, ...(ngDevMode ? [{ debugName: "modelValue" }] : [])); _filterValue = signal(null, ...(ngDevMode ? [{ debugName: "_filterValue" }] : [])); _options = signal([], ...(ngDevMode ? [{ debugName: "_options" }] : [])); startRangeIndex = signal(-1, ...(ngDevMode ? [{ debugName: "startRangeIndex" }] : [])); focusedOptionIndex = signal(-1, ...(ngDevMode ? [{ debugName: "focusedOptionIndex" }] : [])); selectedOptions; clickInProgress = false; get emptyMessageLabel() { return this.emptyMessage || this.config.getTranslation(TranslationKeys.EMPTY_MESSAGE); } get emptyFilterMessageLabel() { return this.emptyFilterMessage || this.config.getTranslation(TranslationKeys.EMPTY_FILTER_MESSAGE); } get isVisibleClearIcon() { return this.modelValue() != null && this.modelValue() !== '' && isNotEmpty(this.modelValue()) && this.showClear && !this.$disabled() && !this.readonly && this.$filled(); } get toggleAllAriaLabel() { return this.config.translation.aria ? this.config.translation.aria[this.allSelected() ? 'selectAll' : 'unselectAll'] : undefined; } get listLabel() { return this.config.getTranslation(TranslationKeys.ARIA)['listLabel']; } getAllVisibleAndNonVisibleOptions() { return this.group ? this.flatOptions(this.options) : this.options || []; } visibleOptions = computed(() => { const options = this.getAllVisibleAndNonVisibleOptions(); const isArrayOfObjects = isArray(options) && ObjectUtils.isObject(options[0]); if (this._filterValue()) { let filteredOptions; if (isArrayOfObjects) { filteredOptions = this.filterService.filter(options, this.searchFields(), this._filterValue(), this.filterMatchMode, this.filterLocale); } else { filteredOptions = options.filter((option) => option.toString().toLocaleLowerCase().includes(this._filterValue().toLocaleLowerCase())); } if (this.group) { 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; }, ...(ngDevMode ? [{ debugName: "visibleOptions" }] : [])); label = computed(() => { let label; const modelValue = this.modelValue(); if (modelValue && modelValue?.length && this.displaySelectedLabel) { if (isNotEmpty(this.maxSelectedLabels) && modelValue?.length > (this.maxSelectedLabels || 0)) { return this.getSelectedItemsLabel(); } else { label = ''; for (let i = 0; i < modelValue.length; i++) { if (i !== 0) { label += ', '; } label += this.getLabelByValue(modelValue[i]); } } } else { label = this.placeholder() || ''; } return label; }, ...(ngDevMode ? [{ debugName: "label" }] : [])); chipSelectedItems = computed(() => { return isNotEmpty(this.maxSelectedLabels) && this.modelValue() && this.modelValue()?.length > (this.maxSelectedLabels || 0) ? this.modelValue()?.slice(0, this.maxSelectedLabels) : this.modelValue(); }, ...(ngDevMode ? [{ debugName: "chipSelectedItems" }] : [])); constructor(zone, filterService, overlayService) { super(); this.zone = zone; this.filterService = filterService; this.overlayService = overlayService; effect(() => { const modelValue = this.modelValue(); const allVisibleAndNonVisibleOptions = this.getAllVisibleAndNonVisibleOptions(); if (allVisibleAndNonVisibleOptions && isNotEmpty(allVisibleAndNonVisibleOptions)) { if (this.optionValue && this.optionLabel && modelValue) { this.selectedOptions = allVisibleAndNonVisibleOptions.filter((option) => modelValue.includes(option[this.optionLabel]) || modelValue.includes(option[this.optionValue])); } else { this.selectedOptions = modelValue; } this.cd.markForCheck(); } }); } onInit() { this.id = this.id || uuid('pn_id_'); this.autoUpdateModel(); if (this.filterBy) { this.filterOptions = { filter: (value) => this.onFilterInputChange(value), reset: () => this.resetFilter() }; } } maxSelectionLimitReached() { return this.selectionLimit && this.modelValue() && this.modelValue().length === this.selectionLimit; } onAfterViewInit() { if (this.overlayVisible) { this.show(); } } onAfterViewChecked() { this.bindDirectiveInstance.setAttrs(this.ptms(['host', 'root'])); if (this.filtered) { this.zone.runOutsideAngular(() => { setTimeout(() => { this.overlayViewChild?.alignOverlay(); }, 1); }); this.filtered = false; } } 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; }, []); } autoUpdateModel() { if (this.selectOnFocus && this.autoOptionFocus && !this.hasSelectedOption()) { this.focusedOptionIndex.set(this.findFirstFocusedOptionIndex()); const value = this.getOptionValue(this.visibleOptions()[this.focusedOptionIndex()]); this.onOptionSelect({ originalEvent: null, option: [value] }); } } /** * Updates the model value. * @group Method */ updateModel(value, event) { this.value = value; this.onModelChange(value); this.writeValue(value); } onInputClick(event) { event.stopPropagation(); event.preventDefault(); this.focusedOptionIndex.set(-1); } onOptionSelect(event, isFocus = false, index = -1) { const { originalEvent, option } = event; if (this.$disabled() || this.isOptionDisabled(option)) { return; } let selected = this.isSelected(option); let value = []; if (selected) { value = this.modelValue().filter((val) => !equals(val, this.getOptionValue(option), this.equalityKey() || '')); } else { value = [...(this.modelValue() || []), this.getOptionValue(option)]; } this.updateModel(value, originalEvent); index !== -1 && this.focusedOptionIndex.set(index); isFocus && focus(this.focusInputViewChild?.nativeElement); this.onChange.emit({ originalEvent: event, value: value, itemValue: option }); } findSelectedOptionIndex() { return this.hasSelectedOption() ? this.visibleOptions().findIndex((option) => this.isValidSelectedOption(option)) : -1; } onOptionSelectRange(event, start = -1, end = -1) { start === -1 && (start = this.findNearestSelectedOptionIndex(end, true)); end === -1 && (end = this.findNearestSelectedOptionIndex(start)); if (start !== -1 && end !== -1) { const rangeStart = Math.min(start, end); const rangeEnd = Math.max(start, end); const value = this.visibleOptions() .slice(rangeStart, rangeEnd + 1) .filter((option) => this.isValidOption(option)) .map((option) => this.getOptionValue(option)); this.updateModel(value, event); } } searchFields() { return (this.filterBy || this.optionLabel || 'label').split(','); } findNearestSelectedOptionIndex(index, firstCheckUp = false) { let matchedOptionIndex = -1; if (this.hasSelectedOption()) { if (firstCheckUp) { matchedOptionIndex = this.findPrevSelectedOptionIndex(index); matchedOptionIndex = matchedOptionIndex === -1 ? this.findNextSelectedOptionIndex(index) : matchedOptionIndex; } else { matchedOptionIndex = this.findNextSelectedOptionIndex(index); matchedOptionIndex = matchedOptionIndex === -1 ? this.findPrevSelectedOptionIndex(index) : matchedOptionIndex; } } return matchedOptionIndex > -1 ? matchedOptionIndex : index; } findPrevSelectedOptionIndex(index) { const matchedOptionIndex = this.hasSelectedOption() && index > 0 ? findLastIndex(this.visibleOptions().slice(0, index), (option) => this.isValidSelectedOption(option)) : -1; return matchedOptionIndex > -1 ? matchedOptionIndex : -1; } findFirstFocusedOptionIndex() { const selectedIndex = this.findFirstSelectedOptionIndex(); return selectedIndex < 0 ? this.findFirstOptionIndex() : selectedIndex; } findFirstOptionIndex() { return this.visibleOptions().findIndex((option) => this.isValidOption(option)); } findFirstSelectedOptionIndex() { return this.hasSelectedOption() ? this.visibleOptions().findIndex((option) => this.isValidSelectedOption(option)) : -1; } findNextSelectedOptionIndex(index) { const matchedOptionIndex = this.hasSelectedOption() && index < this.visibleOptions().length - 1 ? this.visibleOptions() .slice(index + 1) .findIndex((option) => this.isValidSelectedOption(option)) : -1; return matchedOptionIndex > -1 ? matchedOptionIndex + index + 1 : -1; } equalityKey() { return this.optionValue ? null : this.dataKey; } hasSelectedOption() { return isNotEmpty(this.modelValue()); } isValidSelectedOption(option) { return this.isValidOption(option) && this.isSelected(option); } isOptionGroup(option) { return option && (this.group || this.optionGroupLabel) && option.optionGroup && option.group; } isValidOption(option) { return option && !(this.isOptionDisabled(option) || this.isOptionGroup(option)); } isOptionDisabled(option) { if (this.maxSelectionLimitReached() && !this.isSelected(option)) { return true; } return this.optionDisabled ? resolveFieldData(option, this.optionDisabled) : option && option.disabled !== undefined ? option.disabled : false; } isSelected(option) { const optionValue = this.getOptionValue(option); return (this.modelValue() || []).some((value) => equals(value, optionValue, this.equalityKey() || '')); } isOptionMatched(option) { return this.isValidOption(option) && this.getOptionLabel(option).toString().toLocaleLowerCase(this.filterLocale).startsWith(this.searchValue?.toLocaleLowerCase(this.filterLocale)); } isEmpty() { return !this._options() || (this.visibleOptions() && this.visibleOptions().length === 0); } getOptionIndex(index, scrollerOptions) { return this.virtualScrollerDisabled ? index : scrollerOptions && scrollerOptions.getItemOptions(index)['index']; } getAriaPosInset(index) { return ((this.optionGroupLabel ? index - this.visibleOptions() .slice(0, index) .filter((option) => this.isOptionGroup(option)).length : index) + 1); } get ariaSetSize() { return this.visibleOptions().filter((option) => !this.isOptionGroup(option)).length; } getLabelByValue(value) { const options = this.group ? this.flatOptions(this._options()) : this._options() || []; const matchedOption = options.find((option) => !this.isOptionGroup(option) && equals(this.getOptionValue(option), value, this.equalityKey() || '')); return matchedOption ? this.getOptionLabel(matchedOption) : null; } getSelectedItemsLabel() { let pattern = /{(.*?)}/; let message = this.selectedItemsLabel ? this.selectedItemsLabel : this.config.getTranslation(TranslationKeys.SELECTION_MESSAGE); if (pattern.test(message)) { return message.replace(message.match(pattern)[0], this.modelValue().length + ''); } return message; } getOptionLabel(option) { return this.optionLabel ? resolveFieldData(option, this.optionLabel) : option && option.label != undefined ? option.label : option; } getOptionValue(option) { return this.optionValue ? resolveFieldData(option, this.optionValue) : !this.optionLabel && option && option.value !== undefined ? option.value : option; } getOptionGroupLabel(optionGroup) { return this.optionGroupLabel ? resolveFieldData(optionGroup, this.optionGroupLabel) : optionGroup && optionGroup.label != undefined ? optionGroup.label : optionGroup; } getOptionGroupChildren(optionGroup) { return optionGroup ? (this.optionGroupChildren ? resolveFieldData(optionGroup, this.optionGroupChildren) : optionGroup.items) : []; } onKeyDown(event) { if (this.$disabled()) { event.preventDefault(); return; } const metaKey = event.metaKey || event.ctrlKey; switch (event.code) { case 'ArrowDown': this.onArrowDownKey(event); break; case 'ArrowUp': this.onArrowUpKey(event); break; case 'Home': this.onHomeKey(event); break; case 'End': this.onEndKey(event); break; case 'PageDown': this.onPageDownKey(event); break; case 'PageUp': this.onPageUpKey(event); break; case 'Enter': case 'Space': this.onEnterKey(event); break; case 'Escape': this.onEscapeKey(event); break; case 'Tab': this.onTabKey(event); break; case 'ShiftLeft': case 'ShiftRight': this.onShiftKey(); break; default: if (event.code === 'KeyA' && metaKey) { const value = this.visibleOptions() .filter((option) => this.isValidOption(option)) .map((option) => this.getOptionValue(option)); this.updateModel(value, event); event.preventDefault(); break; } if (!metaKey && isPrintableCharacter(event.key)) { !this.overlayVisible && this.show(); this.searchOptions(event, event.key); event.preventDefault(); } break; } } 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':