UNPKG

primeng

Version:

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

1,128 lines (1,112 loc) 127 kB
export * from 'primeng/types/listbox'; import * as i3 from '@angular/cdk/drag-drop'; import { moveItemInArray, DragDropModule, CDK_DRAG_CONFIG } from '@angular/cdk/drag-drop'; import { NgTemplateOutlet } from '@angular/common'; import * as i0 from '@angular/core'; import { Injectable, InjectionToken, forwardRef, input, inject, booleanAttribute, numberAttribute, output, viewChild, contentChild, signal, computed, effect, HostListener, ViewEncapsulation, ChangeDetectionStrategy, Component, NgModule } from '@angular/core'; import { toSignal } from '@angular/core/rxjs-interop'; import * as i2 from '@angular/forms'; import { NG_VALUE_ACCESSOR, FormsModule } from '@angular/forms'; import { Blank } from '@primeicons/angular/blank'; import { Check } from '@primeicons/angular/check'; import { Search } from '@primeicons/angular/search'; import { uuid, isNotEmpty, equals, focus, getFirstFocusableElement, isEmpty, isPrintableCharacter, resolveFieldData, findSingle, findLastIndex, isFunction } from '@primeuix/utils'; import { FilterService, Header, Footer, SharedModule } from 'primeng/api'; import { PARENT_INSTANCE } from 'primeng/basecomponent'; import { BaseEditableHolder } from 'primeng/baseeditableholder'; import * as i1 from 'primeng/bind'; import { Bind, BindModule } from 'primeng/bind'; import { Checkbox } from 'primeng/checkbox'; import { IconField } from 'primeng/iconfield'; import { InputIcon } from 'primeng/inputicon'; import { InputText } from 'primeng/inputtext'; import { Ripple } from 'primeng/ripple'; import { Scroller } from 'primeng/scroller'; import { style as style$1 } from '@primeuix/styles/listbox'; import { BaseStyle } from 'primeng/base'; const style = /*css*/ ` ${style$1} /* For PrimeNG */ .p-listbox.ng-invalid.ng-dirty { border-color: dt('listbox.invalid.border.color'); } .p-listbox-header { display: flex; align-items: center; } .p-listbox-header > .p-iconfield { flex-grow: 1; } .p-listbox-list-container { height: 100%; } /* scrollHeight="flex": the root fills its flex-constrained parent and lays out as a column so the header/filter keep their natural height and the scrollable list shrinks to the remaining space instead of overflowing the container. */ .p-listbox-flex-scrollable { display: flex; flex-direction: column; height: 100%; } .p-listbox-flex-scrollable .p-listbox-list-container { flex: 1; } /* CDK Drag & Drop styles */ /* The single-item ghost and the rows inside the multi-selection preview both use the plain list background, so a multi-drag looks like several copies of the single-drag ghost rather than a block of highlighted (selected) rows. */ .p-listbox-option.cdk-drag-preview, .p-listbox-drag-preview .p-listbox-option { background: dt('listbox.background'); } /* Multi-selection drag preview: dragging an option that is part of a multi-selection previews the whole selection. The preview mirrors the listbox DOM so the theme styles the rows, but the container card chrome (background, border, shadow) is stripped so the floating copy reads as a bare silhouette of the dragged rows rather than a detached panel. */ .p-listbox.p-listbox-drag-preview { max-width: 20rem; pointer-events: none; background: transparent; border: 0 none; box-shadow: none; } .p-listbox-drag-preview .p-listbox-list { padding: 0; } .p-listbox-drag-preview-more { opacity: 0.7; } .p-listbox-dragging .p-listbox-option:not(.cdk-drag-preview) { pointer-events: none !important; } .p-listbox-dragging .p-listbox-option:not(.cdk-drag-preview):hover { background: inherit !important; color: inherit !important; } .cdk-drag-placeholder { pointer-events: none; } `; const classes = { root: ({ instance }) => [ 'p-listbox p-component', { 'p-listbox-striped': instance.striped(), 'p-disabled': instance.$disabled(), 'p-invalid': instance.invalid(), 'p-listbox-fluid': instance.fluid(), 'p-listbox-dragging': instance.isDragging(), 'p-listbox-flex-scrollable': instance.scrollHeight() === 'flex' } ], header: 'p-listbox-header', pcFilter: 'p-listbox-filter', listContainer: 'p-listbox-list-container', list: 'p-listbox-list', optionGroup: 'p-listbox-option-group', option: ({ instance, option, i, scrollerOptions }) => [ 'p-listbox-option', { 'p-listbox-option-selected': instance.isSelected(option) && instance.highlightOnSelect(), 'p-focus': instance.focusedOptionIndex() === instance.getOptionIndex(i, scrollerOptions), 'p-disabled': instance.isOptionDisabled(option) } ], optionCheckIcon: 'p-listbox-option-check-icon', optionBlankIcon: 'p-listbox-option-blank-icon', emptyMessage: 'p-listbox-empty-message', // Root + list + option classes mirror the listbox DOM so the theme styles the preview. // Options are intentionally NOT marked selected, so the preview rows match the plain // single-item drag ghost instead of the highlighted selected background. dragPreview: 'p-listbox p-component p-listbox-drag-preview', dragPreviewOption: 'p-listbox-option', dragPreviewMore: 'p-listbox-option p-listbox-drag-preview-more' }; class ListBoxStyle extends BaseStyle { name = 'listbox'; style = style; classes = classes; static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: ListBoxStyle, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: ListBoxStyle }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: ListBoxStyle, decorators: [{ type: Injectable }] }); /** * * ListBox is used to select one or more values from a list of items. * * [Live Demo](https://www.primeng.org/listbox/) * * @module listboxstyle * */ var ListboxClasses; (function (ListboxClasses) { /** * Class name of the root element */ ListboxClasses["root"] = "p-listbox"; /** * Class name of the header element */ ListboxClasses["header"] = "p-listbox-header"; /** * Class name of the filter element */ ListboxClasses["pcFilter"] = "p-listbox-filter"; /** * Class name of the list container element */ ListboxClasses["listContainer"] = "p-listbox-list-container"; /** * Class name of the list element */ ListboxClasses["list"] = "p-listbox-list"; /** * Class name of the option group element */ ListboxClasses["optionGroup"] = "p-listbox-option-group"; /** * Class name of the option element */ ListboxClasses["option"] = "p-listbox-option"; /** * Class name of the option check icon element */ ListboxClasses["optionCheckIcon"] = "p-listbox-option-check-icon"; /** * Class name of the option blank icon element */ ListboxClasses["optionBlankIcon"] = "p-listbox-option-blank-icon"; /** * Class name of the empty message element */ ListboxClasses["emptyMessage"] = "p-listbox-empty-message"; /** * Class name of the multi-selection drag preview container */ ListboxClasses["dragPreview"] = "p-listbox-drag-preview"; /** * Class name of the overflow row in the multi-selection drag preview */ ListboxClasses["dragPreviewMore"] = "p-listbox-drag-preview-more"; })(ListboxClasses || (ListboxClasses = {})); const LISTBOX_INSTANCE = new InjectionToken('LISTBOX_INSTANCE'); const LISTBOX_VALUE_ACCESSOR = { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => Listbox), multi: true }; /** * ListBox is used to select one or more values from a list of items. * @group Components */ class Listbox extends BaseEditableHolder { componentName = 'Listbox'; hostName = input('', /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "hostName" }] : /* istanbul ignore next */ [])); bindDirectiveInstance = inject(Bind, { self: true }); $pcListbox = inject(LISTBOX_INSTANCE, { optional: true, skipSelf: true }) ?? undefined; filterService = inject(FilterService); onAfterViewChecked() { this.bindDirectiveInstance.setAttrs(this.ptms(['host', 'root'])); } /** * Unique identifier of the component. * @group Props */ id = input(/* @ts-ignore */ ...(ngDevMode ? [undefined, { debugName: "id" }] : /* istanbul ignore next */ [])); /** * Text to display when the search is active. Defaults to global value in i18n translation configuration. * @group Props * @defaultValue '{0} results are available' */ searchMessage = input(/* @ts-ignore */ ...(ngDevMode ? [undefined, { debugName: "searchMessage" }] : /* istanbul ignore next */ [])); /** * Text to display when filtering does not return any results. Defaults to global value in i18n translation configuration. * @group Props * @defaultValue 'No selected item' */ emptySelectionMessage = input(/* @ts-ignore */ ...(ngDevMode ? [undefined, { debugName: "emptySelectionMessage" }] : /* istanbul ignore next */ [])); /** * Text to be displayed in hidden accessible field when options are selected. Defaults to global value in i18n translation configuration. * @group Props * @defaultValue '{0} items selected' */ selectionMessage = input(/* @ts-ignore */ ...(ngDevMode ? [undefined, { debugName: "selectionMessage" }] : /* istanbul ignore next */ [])); /** * Whether to focus on the first visible or selected element when the overlay panel is shown. * @group Props */ autoOptionFocus = input(true, { ...(ngDevMode ? { debugName: "autoOptionFocus" } : /* istanbul ignore next */ {}), transform: booleanAttribute }); /** * Defines a string that labels the input for accessibility. * @group Props */ ariaLabel = input(/* @ts-ignore */ ...(ngDevMode ? [undefined, { debugName: "ariaLabel" }] : /* istanbul ignore next */ [])); /** * When enabled, the focused option is selected. * @group Props */ selectOnFocus = input(undefined, { ...(ngDevMode ? { debugName: "selectOnFocus" } : /* istanbul ignore next */ {}), transform: booleanAttribute }); /** * Locale to use in searching. The default locale is the host environment's current locale. * @group Props */ searchLocale = input(undefined, { ...(ngDevMode ? { debugName: "searchLocale" } : /* istanbul ignore next */ {}), transform: booleanAttribute }); /** * When enabled, the hovered option will be focused. * @group Props */ focusOnHover = input(true, { ...(ngDevMode ? { debugName: "focusOnHover" } : /* istanbul ignore next */ {}), transform: booleanAttribute }); /** * Text to display when filtering. * @group Props */ filterMessage = input(/* @ts-ignore */ ...(ngDevMode ? [undefined, { debugName: "filterMessage" }] : /* istanbul ignore next */ [])); /** * Fields used when filtering the options, defaults to optionLabel. * @group Props */ filterFields = input(/* @ts-ignore */ ...(ngDevMode ? [undefined, { debugName: "filterFields" }] : /* istanbul ignore next */ [])); /** * Defines if data is loaded and interacted with in lazy manner. * @group Props */ lazy = input(false, { ...(ngDevMode ? { debugName: "lazy" } : /* istanbul ignore next */ {}), transform: booleanAttribute }); /** * Whether the data should be loaded on demand during scroll. * @group Props */ virtualScroll = input(undefined, { ...(ngDevMode ? { debugName: "virtualScroll" } : /* istanbul ignore next */ {}), transform: booleanAttribute }); /** * Height of an item in the list for VirtualScrolling. * @group Props */ virtualScrollItemSize = input(undefined, { ...(ngDevMode ? { debugName: "virtualScrollItemSize" } : /* istanbul ignore next */ {}), transform: numberAttribute }); /** * Whether to use the scroller feature. The properties of scroller component can be used like an object in it. * @group Props */ virtualScrollOptions = input(/* @ts-ignore */ ...(ngDevMode ? [undefined, { debugName: "virtualScrollOptions" }] : /* istanbul ignore next */ [])); /** * Height of the viewport in pixels, a scrollbar is defined if height of list exceeds this value. * @group Props */ scrollHeight = input('14rem', /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "scrollHeight" }] : /* istanbul ignore next */ [])); /** * Index of the element in tabbing order. * @group Props */ tabindex = input(0, { ...(ngDevMode ? { debugName: "tabindex" } : /* istanbul ignore next */ {}), transform: numberAttribute }); /** * When specified, allows selecting multiple values. * @group Props */ multiple = input(undefined, { ...(ngDevMode ? { debugName: "multiple" } : /* istanbul ignore next */ {}), transform: booleanAttribute }); /** * Inline style of the list element. * @group Props */ listStyle = input(/* @ts-ignore */ ...(ngDevMode ? [undefined, { debugName: "listStyle" }] : /* istanbul ignore next */ [])); /** * Style class of the list element. * @group Props */ listStyleClass = input(/* @ts-ignore */ ...(ngDevMode ? [undefined, { debugName: "listStyleClass" }] : /* istanbul ignore next */ [])); /** * When present, it specifies that the element value cannot be changed. * @group Props */ readonly = input(undefined, { ...(ngDevMode ? { debugName: "readonly" } : /* istanbul ignore next */ {}), transform: booleanAttribute }); /** * When specified, allows selecting items with checkboxes. * @group Props */ checkbox = input(false, { ...(ngDevMode ? { debugName: "checkbox" } : /* istanbul ignore next */ {}), transform: booleanAttribute }); /** * When specified, displays a filter input at header. * @group Props */ filter = input(false, { ...(ngDevMode ? { debugName: "filter" } : /* istanbul ignore next */ {}), transform: booleanAttribute }); /** * When filtering is enabled, filterBy decides which field or fields (comma separated) to search against. * @group Props */ filterBy = input(/* @ts-ignore */ ...(ngDevMode ? [undefined, { debugName: "filterBy" }] : /* istanbul ignore next */ [])); /** * Defines how the items are filtered. * @group Props */ filterMatchMode = input('contains', /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "filterMatchMode" }] : /* istanbul ignore next */ [])); /** * Locale to use in filtering. The default locale is the host environment's current locale. * @group Props */ filterLocale = input(/* @ts-ignore */ ...(ngDevMode ? [undefined, { debugName: "filterLocale" }] : /* istanbul ignore next */ [])); /** * Defines how multiple items can be selected, when true metaKey needs to be pressed to select or unselect an item and when set to false selection of each item can be toggled individually. On touch enabled devices, metaKeySelection is turned off automatically. * @group Props */ metaKeySelection = input(false, { ...(ngDevMode ? { debugName: "metaKeySelection" } : /* istanbul ignore next */ {}), transform: booleanAttribute }); /** * A property to uniquely identify a value in options. * @group Props */ dataKey = input(/* @ts-ignore */ ...(ngDevMode ? [undefined, { debugName: "dataKey" }] : /* istanbul ignore next */ [])); /** * Whether header checkbox is shown in multiple mode. * @group Props */ showToggleAll = input(true, { ...(ngDevMode ? { debugName: "showToggleAll" } : /* istanbul ignore next */ {}), transform: booleanAttribute }); /** * Name of the label field of an option. * @group Props */ optionLabel = input(/* @ts-ignore */ ...(ngDevMode ? [undefined, { debugName: "optionLabel" }] : /* istanbul ignore next */ [])); /** * Name of the value field of an option. * @group Props */ optionValue = input(/* @ts-ignore */ ...(ngDevMode ? [undefined, { debugName: "optionValue" }] : /* istanbul ignore next */ [])); /** * Name of the options field of an option group. * @group Props */ optionGroupChildren = input('items', /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "optionGroupChildren" }] : /* istanbul ignore next */ [])); /** * Name of the label field of an option group. * @group Props */ optionGroupLabel = input('label', /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "optionGroupLabel" }] : /* istanbul ignore next */ [])); /** * Name of the disabled field of an option or function to determine disabled state. * @group Props */ optionDisabled = input(/* @ts-ignore */ ...(ngDevMode ? [undefined, { debugName: "optionDisabled" }] : /* istanbul ignore next */ [])); /** * Defines a string that labels the filter input. * @group Props */ ariaFilterLabel = input(/* @ts-ignore */ ...(ngDevMode ? [undefined, { debugName: "ariaFilterLabel" }] : /* istanbul ignore next */ [])); /** * Defines placeholder of the filter input. * @group Props */ filterPlaceHolder = input(/* @ts-ignore */ ...(ngDevMode ? [undefined, { debugName: "filterPlaceHolder" }] : /* istanbul ignore next */ [])); /** * Text to display when filtering does not return any results. * @group Props */ emptyFilterMessage = input(/* @ts-ignore */ ...(ngDevMode ? [undefined, { debugName: "emptyFilterMessage" }] : /* istanbul ignore next */ [])); /** * Text to display when there is no data. Defaults to global value in i18n translation configuration. * @group Props */ emptyMessage = input(/* @ts-ignore */ ...(ngDevMode ? [undefined, { debugName: "emptyMessage" }] : /* istanbul ignore next */ [])); /** * Whether to display options as grouped when nested options are provided. * @group Props */ group = input(undefined, { ...(ngDevMode ? { debugName: "group" } : /* istanbul ignore next */ {}), transform: booleanAttribute }); /** * An array of selectitems to display as the available options. * @group Props */ options = input(/* @ts-ignore */ ...(ngDevMode ? [undefined, { debugName: "options" }] : /* istanbul ignore next */ [])); /** * When specified, filter displays with this value. * @group Props */ filterValue = input(/* @ts-ignore */ ...(ngDevMode ? [undefined, { debugName: "filterValue" }] : /* istanbul ignore next */ [])); /** * Whether all data is selected. * @group Props */ selectAll = input(/* @ts-ignore */ ...(ngDevMode ? [undefined, { debugName: "selectAll" }] : /* istanbul ignore next */ [])); /** * Whether to displays rows with alternating colors. * @group Props * @defaultValue false */ striped = input(false, { ...(ngDevMode ? { debugName: "striped" } : /* istanbul ignore next */ {}), transform: booleanAttribute }); /** * Whether the selected option will be add highlight class. * @group Props * @defaultValue true */ highlightOnSelect = input(true, { ...(ngDevMode ? { debugName: "highlightOnSelect" } : /* istanbul ignore next */ {}), transform: booleanAttribute }); /** * Whether the selected option will be shown with a check mark. * @group Props * @defaultValue false */ checkmark = input(false, { ...(ngDevMode ? { debugName: "checkmark" } : /* istanbul ignore next */ {}), transform: booleanAttribute }); /** * Whether to enable dragdrop based reordering. * @group Props */ dragdrop = input(false, { ...(ngDevMode ? { debugName: "dragdrop" } : /* istanbul ignore next */ {}), transform: booleanAttribute }); /** * Array to use for CDK drop list data binding. When not provided, uses options array. * @group Props */ dropListData = input(/* @ts-ignore */ ...(ngDevMode ? [undefined, { debugName: "dropListData" }] : /* istanbul ignore next */ [])); /** * Spans 100% width of the container when enabled. * @defaultValue undefined * @group Props */ fluid = input(undefined, { ...(ngDevMode ? { debugName: "fluid" } : /* istanbul ignore next */ {}), transform: booleanAttribute }); /** * Callback to invoke on value change. * @param {ListboxChangeEvent} event - Custom change event. * @group Emits */ onChange = output(); /** * Callback to invoke when option is clicked. * @param {ListboxClickEvent} event - Custom click event. * @group Emits */ onClick = output(); /** * Callback to invoke when option is double clicked. * @param {ListboxDoubleClickEvent} event - Custom double click event. * @group Emits */ onDblClick = output(); /** * Callback to invoke when data is filtered. * @param {ListboxFilterEvent} event - Custom filter event. * @group Emits */ onFilter = output(); /** * Callback to invoke when component receives focus. * @param {FocusEvent} event - Focus event. * @group Emits */ onFocus = output(); /** * Callback to invoke when component loses focus. * @param {FocusEvent} event - Blur event. * @group Emits */ onBlur = output(); /** * Callback to invoke when all data is selected. * @param {ListboxSelectAllChangeEvent} event - Custom select event. * @group Emits */ onSelectAllChange = output(); /** * Emits on lazy load. * @param {ScrollerLazyLoadEvent} event - Scroller lazy load event. * @group Emits */ onLazyLoad = output(); /** * Emits on item is dropped. * @param {CdkDragDrop<string[]>} event - Scroller lazy load event. * @group Emits */ onDrop = output(); headerCheckboxViewChild = viewChild('headerchkbox', /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "headerCheckboxViewChild" }] : /* istanbul ignore next */ [])); filterViewChild = viewChild('filterInput', /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "filterViewChild" }] : /* istanbul ignore next */ [])); lastHiddenFocusableElement = viewChild('lastHiddenFocusableElement', /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "lastHiddenFocusableElement" }] : /* istanbul ignore next */ [])); firstHiddenFocusableElement = viewChild('firstHiddenFocusableElement', /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "firstHiddenFocusableElement" }] : /* istanbul ignore next */ [])); scroller = viewChild('scroller', /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "scroller" }] : /* istanbul ignore next */ [])); listViewChild = viewChild('list', /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "listViewChild" }] : /* istanbul ignore next */ [])); containerViewChild = viewChild('container', /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "containerViewChild" }] : /* istanbul ignore next */ [])); headerFacet = contentChild(Header, { ...(ngDevMode ? { debugName: "headerFacet" } : /* istanbul ignore next */ {}), descendants: false }); footerFacet = contentChild(Footer, { ...(ngDevMode ? { debugName: "footerFacet" } : /* istanbul ignore next */ {}), descendants: false }); /** * Custom item template. * @param {ListboxItemTemplateContext} context - item context. * @see {@link ListboxItemTemplateContext} * @group Templates */ itemTemplate = contentChild('item', { ...(ngDevMode ? { debugName: "itemTemplate" } : /* istanbul ignore next */ {}), descendants: false }); /** * Custom group template. * @param {ListboxGroupTemplateContext} context - group context. * @see {@link ListboxGroupTemplateContext} * @group Templates */ groupTemplate = contentChild('group', { ...(ngDevMode ? { debugName: "groupTemplate" } : /* istanbul ignore next */ {}), descendants: false }); /** * Custom header template. * @param {ListboxHeaderTemplateContext} context - header context. * @see {@link ListboxHeaderTemplateContext} * @group Templates */ headerTemplate = contentChild('header', { ...(ngDevMode ? { debugName: "headerTemplate" } : /* istanbul ignore next */ {}), descendants: false }); /** * Custom filter template. * @param {ListboxFilterTemplateContext} context - filter context. * @see {@link ListboxFilterTemplateContext} * @group Templates */ filterTemplate = contentChild('filter', { ...(ngDevMode ? { debugName: "filterTemplate" } : /* istanbul ignore next */ {}), descendants: false }); /** * Custom footer template. * @param {ListboxFooterTemplateContext} context - footer context. * @see {@link ListboxFooterTemplateContext} * @group Templates */ footerTemplate = contentChild('footer', { ...(ngDevMode ? { debugName: "footerTemplate" } : /* istanbul ignore next */ {}), descendants: false }); /** * Custom empty filter message template. * @group Templates */ emptyFilterTemplate = contentChild('emptyfilter', { ...(ngDevMode ? { debugName: "emptyFilterTemplate" } : /* istanbul ignore next */ {}), descendants: false }); /** * Custom empty message template. * @group Templates */ emptyTemplate = contentChild('empty', { ...(ngDevMode ? { debugName: "emptyTemplate" } : /* istanbul ignore next */ {}), descendants: false }); /** * Custom filter icon template. * @group Templates */ filterIconTemplate = contentChild('filtericon', { ...(ngDevMode ? { debugName: "filterIconTemplate" } : /* istanbul ignore next */ {}), descendants: false }); /** * Custom check icon template. * @param {ListboxCheckIconTemplateContext} context - check icon context. * @see {@link ListboxCheckIconTemplateContext} * @group Templates */ checkIconTemplate = contentChild('checkicon', { ...(ngDevMode ? { debugName: "checkIconTemplate" } : /* istanbul ignore next */ {}), descendants: false }); /** * Custom checkmark icon template. * @param {ListboxCheckmarkTemplateContext} context - checkmark context. * @see {@link ListboxCheckmarkTemplateContext} * @group Templates */ checkmarkTemplate = contentChild('checkmark', { ...(ngDevMode ? { debugName: "checkmarkTemplate" } : /* istanbul ignore next */ {}), descendants: false }); /** * Custom loader template. * @param {ListboxLoaderTemplateContext} context - loader context. * @see {@link ListboxLoaderTemplateContext} * @group Templates */ loaderTemplate = contentChild('loader', { ...(ngDevMode ? { debugName: "loaderTemplate" } : /* istanbul ignore next */ {}), descendants: false }); _filterValue = signal(null, /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "_filterValue" }] : /* istanbul ignore next */ [])); _filteredOptions; filterOptions; filtered; value; optionTouched; focus; headerCheckboxFocus; translation = toSignal(this.config.translationObserver, { initialValue: this.config.translation }); focused; scrollerTabIndex = '0'; _componentStyle = inject(ListBoxStyle); _options = signal(null, /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "_options" }] : /* istanbul ignore next */ [])); startRangeIndex = signal(-1, /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "startRangeIndex" }] : /* istanbul ignore next */ [])); focusedOptionIndex = signal(-1, /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "focusedOptionIndex" }] : /* istanbul ignore next */ [])); isDragging = signal(false, /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "isDragging" }] : /* istanbul ignore next */ [])); searchValue; searchTimeout; _internalId = uuid('pn_id_'); $id = computed(() => this.id() || this._internalId, /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "$id" }] : /* istanbul ignore next */ [])); $tabindex = computed(() => this.tabindex() ?? 0, /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "$tabindex" }] : /* istanbul ignore next */ [])); /** * Computed property for hidden focusable element tabindex */ hiddenFocusableTabindex = computed(() => (!this.$disabled() ? this.$tabindex() : -1), /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "hiddenFocusableTabindex" }] : /* istanbul ignore next */ [])); /** * Computed property for header section visibility */ hasHeader = computed(() => !!this.headerFacet() || !!this.headerTemplate(), /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "hasHeader" }] : /* istanbul ignore next */ [])); /** * Computed property for footer section visibility */ hasFooter = computed(() => !!this.footerFacet() || !!this.footerTemplate(), /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "hasFooter" }] : /* istanbul ignore next */ [])); /** * Computed property for checkbox/filter header visibility */ hasCheckboxOrFilter = computed(() => (this.checkbox() && this.multiple() && this.showToggleAll()) || this.filter(), /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "hasCheckboxOrFilter" }] : /* istanbul ignore next */ [])); /** * Computed property for showing checkbox toggle all */ showCheckboxToggleAll = computed(() => this.checkbox() && this.multiple() && this.showToggleAll(), /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "showCheckboxToggleAll" }] : /* istanbul ignore next */ [])); /** * Computed property for showing option checkbox */ showOptionCheckbox = computed(() => this.checkbox() && this.multiple(), /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "showOptionCheckbox" }] : /* istanbul ignore next */ [])); /** * Computed property for showing filtered empty message */ showFilteredEmptyMessage = computed(() => this.hasFilter() && this.isEmpty(), /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "showFilteredEmptyMessage" }] : /* istanbul ignore next */ [])); /** * Computed property for showing empty message */ showEmptyMessage = computed(() => !this.hasFilter() && this.isEmpty(), /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "showEmptyMessage" }] : /* istanbul ignore next */ [])); /** * Computed property for aria-activedescendant */ ariaActiveDescendant = computed(() => (this.focused ? this.focusedOptionId() : undefined), /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "ariaActiveDescendant" }] : /* istanbul ignore next */ [])); /** * Computed property for header template context */ headerTemplateContext = computed(() => ({ $implicit: this.modelValue(), options: this.visibleOptions() }), /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "headerTemplateContext" }] : /* istanbul ignore next */ [])); /** * Computed property for footer template context */ footerTemplateContext = computed(() => ({ $implicit: this.modelValue(), options: this.visibleOptions() }), /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "footerTemplateContext" }] : /* istanbul ignore next */ [])); /** * Computed property for filter input value */ filterInputValue = computed(() => this._filterValue() || '', /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "filterInputValue" }] : /* istanbul ignore next */ [])); /** * Computed property for disabled attribute */ disabledAttr = computed(() => (this.$disabled() ? '' : undefined), /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "disabledAttr" }] : /* istanbul ignore next */ [])); /** * Computed property for list id */ listId = computed(() => `${this.$id()}_list`, /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "listId" }] : /* istanbul ignore next */ [])); /** * Computed property for scroller style */ scrollerStyle = computed(() => ({ height: this.scrollHeight() === 'flex' ? '100%' : this.scrollHeight() }), /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "scrollerStyle" }] : /* istanbul ignore next */ [])); /** * Computed property for showing default filter empty message */ showDefaultFilterEmptyMessage = computed(() => !this.emptyFilterTemplate() && !this.emptyTemplate(), /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "showDefaultFilterEmptyMessage" }] : /* istanbul ignore next */ [])); /** * Computed property for empty or filter template */ emptyOrFilterTemplate = computed(() => this.emptyFilterTemplate() || this.emptyTemplate(), /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "emptyOrFilterTemplate" }] : /* istanbul ignore next */ [])); /** * Computed property for build in items context */ buildInItemsContext = computed(() => ({ $implicit: this.visibleOptions(), options: {} }), /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "buildInItemsContext" }] : /* istanbul ignore next */ [])); /** * Computed property for dragdrop disabled */ dragdropDisabled = computed(() => !this.dragdrop(), /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "dragdropDisabled" }] : /* istanbul ignore next */ [])); /** * Computed property for check icon context */ allSelectedContext = computed(() => ({ $implicit: this.allSelected() }), /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "allSelectedContext" }] : /* istanbul ignore next */ [])); /** * Computed property for showing default checkmark */ showDefaultCheckmark = computed(() => !this.checkmarkTemplate(), /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "showDefaultCheckmark" }] : /* istanbul ignore next */ [])); /** * Computed property for showing default item label */ showDefaultItemLabel = computed(() => !this.itemTemplate(), /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "showDefaultItemLabel" }] : /* istanbul ignore next */ [])); /** * Computed property for showing default group label */ showDefaultGroupLabel = computed(() => !this.groupTemplate(), /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "showDefaultGroupLabel" }] : /* istanbul ignore next */ [])); /** * Computed property for showing filter icon */ showDefaultFilterIcon = computed(() => !this.filterIconTemplate(), /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "showDefaultFilterIcon" }] : /* istanbul ignore next */ [])); /** * Computed property for stable CDK drop list data reference */ cdkDropData = computed(() => this.dropListData() || this._options(), /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "cdkDropData" }] : /* istanbul ignore next */ [])); checkboxVariant = computed(() => (this.config.inputVariant() === 'filled' ? 'filled' : 'outlined'), /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "checkboxVariant" }] : /* istanbul ignore next */ [])); listContainerStyle = computed(() => { const listStyle = this.listStyle(); const scrollHeight = this.scrollHeight(); const maxHeight = this.virtualScroll() || scrollHeight === 'flex' ? 'auto' : scrollHeight || 'auto'; return { ...listStyle, 'max-height': maxHeight }; }, /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "listContainerStyle" }] : /* istanbul ignore next */ [])); focusedOptionId = computed(() => (this.focusedOptionIndex() !== -1 ? `${this.$id()}_${this.focusedOptionIndex()}` : null), /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "focusedOptionId" }] : /* istanbul ignore next */ [])); filterTabindex = computed(() => (!this.$disabled() && !this.focused ? this.$tabindex() : -1), /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "filterTabindex" }] : /* istanbul ignore next */ [])); filterResultMessageText = computed(() => { return isNotEmpty(this.visibleOptions()) ? this.filterMessageText().replaceAll('{0}', String(this.visibleOptions().length)) : this.emptyFilterMessageText(); }, /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "filterResultMessageText" }] : /* istanbul ignore next */ [])); filterMessageText = computed(() => { const t = this.translation(); return this.filterMessage() || t?.searchMessage || ''; }, /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "filterMessageText" }] : /* istanbul ignore next */ [])); searchMessageText = computed(() => { const t = this.translation(); return this.searchMessage() || t?.searchMessage || ''; }, /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "searchMessageText" }] : /* istanbul ignore next */ [])); emptyFilterMessageText = computed(() => { const t = this.translation(); return this.emptyFilterMessage() || t?.emptySearchMessage || t?.emptyFilterMessage || ''; }, /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "emptyFilterMessageText" }] : /* istanbul ignore next */ [])); selectionMessageText = computed(() => { const t = this.translation(); return this.selectionMessage() || t?.selectionMessage || ''; }, /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "selectionMessageText" }] : /* istanbul ignore next */ [])); emptySelectionMessageText = computed(() => { const t = this.translation(); return this.emptySelectionMessage() || t?.emptySelectionMessage || ''; }, /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "emptySelectionMessageText" }] : /* istanbul ignore next */ [])); selectedMessageText = computed(() => { return this.hasSelectedOption() ? this.selectionMessageText().replaceAll('{0}', this.multiple() ? String(this.modelValue().length) : '1') : this.emptySelectionMessageText(); }, /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "selectedMessageText" }] : /* istanbul ignore next */ [])); ariaSetSize = computed(() => this.visibleOptions().filter((option) => !this.isOptionGroup(option)).length, /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "ariaSetSize" }] : /* istanbul ignore next */ [])); virtualScrollerDisabled = computed(() => !this.virtualScroll(), /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "virtualScrollerDisabled" }] : /* istanbul ignore next */ [])); searchFields = computed(() => this.filterBy()?.split(',') || this.filterFields() || [this.optionLabel()], /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "searchFields" }] : /* istanbul ignore next */ [])); toggleAllAriaLabel = computed(() => { const t = this.translation(); return t?.aria ? t.aria[this.allSelected() ? 'selectAll' : 'unselectAll'] : undefined; }, /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "toggleAllAriaLabel" }] : /* istanbul ignore next */ [])); onHostFocusOut(event) { this.onFocusout(event); } visibleOptions = computed(() => { const options = this.group() ? this.flatOptions(this._options()) : this._options() || []; return this._filterValue() ? this.filterService.filter(options, this.searchFields(), this._filterValue(), this.filterMatchMode(), this.filterLocale()) : options; }, /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "visibleOptions" }] : /* istanbul ignore next */ [])); constructor() { super(); // Sync options input to internal signal effect(() => { const opts = this.options(); if (opts !== undefined) { this._options.set(opts); } }); // Sync filterValue input to internal signal effect(() => { const fv = this.filterValue(); if (fv !== undefined) { this._filterValue.set(fv); } }); } onInit() { this.autoUpdateModel(); if (this.filterBy()) { this.filterOptions = { filter: (value) => this.onFilterChange(value), reset: () => this.resetFilter() }; } } 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.multiple()) { const focusedOptionIndex = this.findFirstFocusedOptionIndex(); this.focusedOptionIndex.set(focusedOptionIndex); this.onOptionSelect(null, this.visibleOptions()[this.focusedOptionIndex()]); } } /** * Updates the model value. * @group Method */ updateModel(value, event) { this.value = value; this.writeModelValue(value); this.onModelChange(value); this.onChange.emit({ originalEvent: event, value: this.value }); } removeOption(option) { return this.modelValue().filter((val) => !equals(val, this.getOptionValue(option), this.equalityKey() || '')); } onOptionSelect(event, option, index = -1) { if (this.$disabled() || this.isOptionDisabled(option) || this.readonly()) { return; } event && this.onClick.emit({ originalEvent: event, option, value: this.value }); this.multiple() ? this.onOptionSelectMultiple(event, option) : this.onOptionSelectSingle(event, option); this.optionTouched = false; index !== -1 && this.focusedOptionIndex.set(index); } onOptionSelectMultiple(event, option) { let selected = this.isSelected(option); let value = []; let metaSelection = this.optionTouched ? false : this.metaKeySelection(); if (metaSelection) { let metaKey = event.metaKey || event.ctrlKey; if (selected) { value = metaKey ? this.removeOption(option) : [this.getOptionValue(option)]; } else { value = metaKey ? this.modelValue() || [] : []; value = [...(value || []), this.getOptionValue(option)]; } } else { value = selected ? this.removeOption(option) : [...(this.modelValue() || []), this.getOptionValue(option)]; } this.updateModel(value, event); } onOptionSelectSingle(event, option) { let selected = this.isSelected(option); let valueChanged = false; let value = null; let metaSelection = this.optionTouched ? false : this.metaKeySelection(); if (metaSelection) { let metaKey = event.metaKey || event.ctrlKey; if (selected) { if (metaKey) { value = null; valueChanged = true; } } else { value = this.getOptionValue(option); valueChanged = true; } } else { value = selected ? null : this.getOptionValue(option); valueChanged = true; } if (valueChanged) { this.updateModel(value, event); } } 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); } } onToggleAll(event) { if (this.$disabled() || this.readonly()) { return; } focus(this.headerCheckboxViewChild()?.nativeElement); if (this.selectAll() !== null && this.selectAll() !== undefined) { this.onSelectAllChange.emit({ originalEvent: event, checked: !this.allSelected() }); } else { const value = this.allSelected() ? [] : this.visibleOptions() .filter((option) => this.isValidOption(option)) .map((option) => this.getOptionValue(option)); this.updateModel(value, event); } } allSelected() { const selectAllValue = this.selectAll(); return selectAllValue !== null && selectAllValue !== undefined ? selectAllValue : isNotEmpty(this.visibleOptions()) && this.visibleOptions().every((option) => this.isOptionGroup(option) || this.isOptionDisabled(option) || this.isSelected(option)); } onOptionTouchEnd() { if (this.$disabled()) { return; } this.optionTouched = true; } onOptionMouseDown(event, index) { this.changeFocusedOptionIndex(event, index); } onOptionMouseEnter(event, index) { if (this.focusOnHover() && this.focused) { this.changeFocusedOptionIndex(event, index); } } onOptionDoubleClick(event, option) { if (this.$disabled() || this.isOptionDisabled(option) || this.readonly()) { return; } this.onDblClick.emit({ originalEvent: event, option: option, value: this.value }); } onFirstHiddenFocus(event) { focus(this.listViewChild()?.nativeElement); const firstFocusableEl = getFirstFocusableElement(this.el?.nativeElement, ':not([data-p-hidden-focusable="true"])'); const lastEl = this.lastHiddenFocusableElement(); const firstEl = this.firstHiddenFocusableElement(); lastEl?.nativeElement && (lastEl.nativeElement.tabIndex = isEmpty(firstFocusableEl) ? -1 : undefined); firstEl?.nativeElement && (firstEl.nativeElement.tabIndex = -1); } onLastHiddenFocus(event) { const relatedTarget = event.relatedTarget; const lastEl = this.lastHiddenFocusableElement(); const firstEl = this.firstHiddenFocusableElement(); if (relatedTarget === this.listViewChild()?.nativeElement) { const firstFocusableEl = getFirstFocusableElement(this.el?.nativeElement, ':not([data-p-hidden-focusable="true"])'); focus(firstFocusableEl); firstEl?.nativeElement && (firstEl.nativeElement.tabIndex = undefined); } else { focus(firstEl?.nativeElement); } lastEl?.nativeElement && (lastEl.nativeElement.tabIndex = -1); } onFocusout(event) { const lastEl = this.lastHiddenFocusableElement(); const firstEl = this.firstHiddenFocusableElement(); if (!this.el.nativeElement.contains(event.relatedTarget) && lastEl && firstEl) { firstEl.nativeElement.tabIndex = lastEl.nativeElement.tabIndex = undefined; this.scrollerTabIndex = '0'; } } onListFocus(event) { this.focused = true; const focusedOptionIndex = this.focusedOptionIndex() !== -1 ? this.focusedOptionIndex() : this.autoOptionFocus() ? this.findFirstFocusedOptionIndex() : this.findSelectedOptionIndex(); this.focusedOptionIndex.set(focusedOptionIndex); this.scrollInView(focusedOptionIndex); this.onFocus.emit(event); this.scrollerTabIndex = '-1'; } onListBlur(event) { this.focused = false; this.focusedOptionIndex.set(-1); this.startRangeIndex.set(-1); this.searchValue = ''; this.onBlur.emit(event); } onHeaderCheckboxKeyDown(event) { if (this.$disabled()) { event.preventDefault(); return; } switch (event.code) { case 'Space': this.onToggleAll(event); break; case 'Enter': this.onToggleAll(event); break; case 'Tab': this.onHeaderCheckboxTabKeyDown(event); break; default: break; } } onHeaderCheckboxTabKeyDown(event) { focus(this.listViewChild()?.nativeElement); event.preventDefault(); } onFilterChange(event) { let value = event.target.value?.trim(); this._filterValue.set(value); this.focusedOptionIndex.set(-1); this.startRangeIndex.set(-1); this.onFilter.emit({ originalEvent: event, filter: this._filterValue() }); !this.virtualScrollerDisabled() && this.scroller()?.scrollToIndex(0); } onFilterBlur(event) { this.focusedOptionIndex.set(-1); this.startRangeIndex.set(-1); } onListKeyDown(event) { 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;