@ng-select/ng-select
Version:
Angular select, multiselect and autocomplete with Signal Forms, Reactive Forms and Template-driven Forms support
5,267 lines • 251 kB
JavaScript
import * as i0 from '@angular/core';
import { inject, ElementRef, input, effect, Directive, TemplateRef, Injectable, booleanAttribute, output, Renderer2, NgZone, DestroyRef, viewChild, computed, ViewEncapsulation, ChangeDetectionStrategy, Component, signal, afterEveryRender, runInInjectionContext, Injector, InjectionToken, HostAttributeToken, linkedSignal, numberAttribute, model, contentChild, contentChildren, forwardRef, ViewContainerRef, ChangeDetectorRef, untracked, isDevMode, HostListener, NgModule } from '@angular/core';
import { FlexibleConnectedPositionStrategy, OverlayContainer, createFlexibleConnectedPositionStrategy, createOverlayRef, createNoopScrollStrategy } from '@angular/cdk/overlay';
import { DOCUMENT, NgTemplateOutlet, NgClass } from '@angular/common';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { animationFrameScheduler, asapScheduler, fromEvent, Subject } from 'rxjs';
import { auditTime, tap, debounceTime, filter, map } from 'rxjs/operators';
import { EventPhase } from '@angular/core/primitives/event-dispatch';
import { NG_VALUE_ACCESSOR } from '@angular/forms';
import { TemplatePortal } from '@angular/cdk/portal';
/**
* @deprecated Default labels are plain text via interpolation. Prefer `{{ label }}` or
* custom templates (`ng-label-tmp` / `ng-option-tmp`) for rich markup. Kept for public API
* compatibility; always writes `textContent` (the `escape` input is ignored).
*
* @since 3.0.2
*/
class NgItemLabelDirective {
/**
* Creates an instance of NgItemLabelDirective.
*
* @since 3.0.2
*/
constructor() {
this.element = inject(ElementRef);
this.ngItemLabel = input(/* @ts-ignore */
...(ngDevMode ? [undefined, { debugName: "ngItemLabel" }] : /* istanbul ignore next */ []));
/** @deprecated Ignored — labels are always written as text. */
this.escape = input(true, /* @ts-ignore */
...(ngDevMode ? [{ debugName: "escape" }] : /* istanbul ignore next */ []));
effect(() => {
this.element.nativeElement.textContent = this.ngItemLabel() ?? '';
});
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: NgItemLabelDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.1.4", type: NgItemLabelDirective, isStandalone: true, selector: "[ngItemLabel]", inputs: { ngItemLabel: { classPropertyName: "ngItemLabel", publicName: "ngItemLabel", isSignal: true, isRequired: false, transformFunction: null }, escape: { classPropertyName: "escape", publicName: "escape", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: NgItemLabelDirective, decorators: [{
type: Directive,
args: [{
selector: '[ngItemLabel]',
standalone: true,
}]
}], ctorParameters: () => [], propDecorators: { ngItemLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ngItemLabel", required: false }] }], escape: [{ type: i0.Input, args: [{ isSignal: true, alias: "escape", required: false }] }] } });
/**
* Marks a template as the custom option template for ng-select.
*
* @since 3.0.0
*/
class NgOptionTemplateDirective {
constructor() {
this.template = inject((TemplateRef));
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: NgOptionTemplateDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.4", type: NgOptionTemplateDirective, isStandalone: true, selector: "[ng-option-tmp]", ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: NgOptionTemplateDirective, decorators: [{
type: Directive,
args: [{
// eslint-disable-next-line @angular-eslint/directive-selector
selector: '[ng-option-tmp]',
standalone: true,
}]
}] });
/**
* Marks a template as the custom optgroup template for ng-select.
*
* @since 3.0.0
*/
class NgOptgroupTemplateDirective {
constructor() {
this.template = inject((TemplateRef));
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: NgOptgroupTemplateDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.4", type: NgOptgroupTemplateDirective, isStandalone: true, selector: "[ng-optgroup-tmp]", ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: NgOptgroupTemplateDirective, decorators: [{
type: Directive,
args: [{
// eslint-disable-next-line @angular-eslint/directive-selector
selector: '[ng-optgroup-tmp]',
standalone: true,
}]
}] });
/**
* Marks a template as the custom label template for ng-select.
*
* @since 3.0.0
*/
class NgLabelTemplateDirective {
constructor() {
this.template = inject((TemplateRef));
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: NgLabelTemplateDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.4", type: NgLabelTemplateDirective, isStandalone: true, selector: "[ng-label-tmp]", ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: NgLabelTemplateDirective, decorators: [{
type: Directive,
args: [{
// eslint-disable-next-line @angular-eslint/directive-selector
selector: '[ng-label-tmp]',
standalone: true,
}]
}] });
/**
* Marks a template as the custom multi label template for ng-select.
*
* @since 3.0.0
*/
class NgMultiLabelTemplateDirective {
constructor() {
this.template = inject((TemplateRef));
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: NgMultiLabelTemplateDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.4", type: NgMultiLabelTemplateDirective, isStandalone: true, selector: "[ng-multi-label-tmp]", ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: NgMultiLabelTemplateDirective, decorators: [{
type: Directive,
args: [{
// eslint-disable-next-line @angular-eslint/directive-selector
selector: '[ng-multi-label-tmp]',
standalone: true,
}]
}] });
/**
* Marks a template as the custom header template for ng-select.
*
* @since 3.0.0
*/
class NgHeaderTemplateDirective {
constructor() {
this.template = inject((TemplateRef));
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: NgHeaderTemplateDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.4", type: NgHeaderTemplateDirective, isStandalone: true, selector: "[ng-header-tmp]", ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: NgHeaderTemplateDirective, decorators: [{
type: Directive,
args: [{
// eslint-disable-next-line @angular-eslint/directive-selector
selector: '[ng-header-tmp]',
standalone: true,
}]
}] });
/**
* Marks a template as the custom footer template for ng-select.
*
* @since 3.0.0
*/
class NgFooterTemplateDirective {
constructor() {
this.template = inject((TemplateRef));
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: NgFooterTemplateDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.4", type: NgFooterTemplateDirective, isStandalone: true, selector: "[ng-footer-tmp]", ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: NgFooterTemplateDirective, decorators: [{
type: Directive,
args: [{
// eslint-disable-next-line @angular-eslint/directive-selector
selector: '[ng-footer-tmp]',
standalone: true,
}]
}] });
/**
* Marks a template as the custom not found template for ng-select.
*
* @since 3.0.0
*/
class NgNotFoundTemplateDirective {
constructor() {
this.template = inject((TemplateRef));
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: NgNotFoundTemplateDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.4", type: NgNotFoundTemplateDirective, isStandalone: true, selector: "[ng-notfound-tmp]", ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: NgNotFoundTemplateDirective, decorators: [{
type: Directive,
args: [{
// eslint-disable-next-line @angular-eslint/directive-selector
selector: '[ng-notfound-tmp]',
standalone: true,
}]
}] });
/**
* Marks a template as the custom placeholder template for ng-select.
*
* @since 13.6.0
*/
class NgPlaceholderTemplateDirective {
constructor() {
this.template = inject((TemplateRef));
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: NgPlaceholderTemplateDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.4", type: NgPlaceholderTemplateDirective, isStandalone: true, selector: "[ng-placeholder-tmp]", ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: NgPlaceholderTemplateDirective, decorators: [{
type: Directive,
args: [{
// eslint-disable-next-line @angular-eslint/directive-selector
selector: '[ng-placeholder-tmp]',
standalone: true,
}]
}] });
/**
* Marks a template as the custom type to search template for ng-select.
*
* @since 3.0.0
*/
class NgTypeToSearchTemplateDirective {
constructor() {
this.template = inject((TemplateRef));
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: NgTypeToSearchTemplateDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.4", type: NgTypeToSearchTemplateDirective, isStandalone: true, selector: "[ng-typetosearch-tmp]", ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: NgTypeToSearchTemplateDirective, decorators: [{
type: Directive,
args: [{
// eslint-disable-next-line @angular-eslint/directive-selector
selector: '[ng-typetosearch-tmp]',
standalone: true,
}]
}] });
/**
* Marks a template as the custom loading text template for ng-select.
*
* @since 3.0.0
*/
class NgLoadingTextTemplateDirective {
constructor() {
this.template = inject((TemplateRef));
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: NgLoadingTextTemplateDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.4", type: NgLoadingTextTemplateDirective, isStandalone: true, selector: "[ng-loadingtext-tmp]", ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: NgLoadingTextTemplateDirective, decorators: [{
type: Directive,
args: [{
// eslint-disable-next-line @angular-eslint/directive-selector
selector: '[ng-loadingtext-tmp]',
standalone: true,
}]
}] });
/**
* Marks a template as the custom tag template for ng-select.
*
* @since 3.0.0
*/
class NgTagTemplateDirective {
constructor() {
this.template = inject((TemplateRef));
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: NgTagTemplateDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.4", type: NgTagTemplateDirective, isStandalone: true, selector: "[ng-tag-tmp]", ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: NgTagTemplateDirective, decorators: [{
type: Directive,
args: [{
// eslint-disable-next-line @angular-eslint/directive-selector
selector: '[ng-tag-tmp]',
standalone: true,
}]
}] });
/**
* Marks a template as the custom loading spinner template for ng-select.
*
* @since 3.0.0
*/
class NgLoadingSpinnerTemplateDirective {
constructor() {
this.template = inject((TemplateRef));
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: NgLoadingSpinnerTemplateDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.4", type: NgLoadingSpinnerTemplateDirective, isStandalone: true, selector: "[ng-loadingspinner-tmp]", ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: NgLoadingSpinnerTemplateDirective, decorators: [{
type: Directive,
args: [{
// eslint-disable-next-line @angular-eslint/directive-selector
selector: '[ng-loadingspinner-tmp]',
standalone: true,
}]
}] });
/**
* Marks a template as the custom clear button template for ng-select.
*
* @since 14.2.0
*/
class NgClearButtonTemplateDirective {
constructor() {
this.template = inject((TemplateRef));
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: NgClearButtonTemplateDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.4", type: NgClearButtonTemplateDirective, isStandalone: true, selector: "[ng-clearbutton-tmp]", ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: NgClearButtonTemplateDirective, decorators: [{
type: Directive,
args: [{
// eslint-disable-next-line @angular-eslint/directive-selector
selector: '[ng-clearbutton-tmp]',
standalone: true,
}]
}] });
const EVENT_SCHEDULER = typeof requestAnimationFrame !== 'undefined' ? animationFrameScheduler : asapScheduler;
/**
* Determines whether an event originated inside the select host or dropdown panel.
*
* @param event - The DOM event to inspect.
* @param select - The ng-select host element.
* @param dropdown - The rendered dropdown panel element.
* @returns Whether the event originated inside either element.
*
* @since 24.0.5
*/
function isEventInside(event, select, dropdown) {
// composedPath preserves the real origin across shadow DOM boundaries.
const path = event.path || event.composedPath?.();
if (path?.length) {
return path.includes(select) || path.includes(dropdown);
}
return select.contains(event.target) || dropdown.contains(event.target);
}
/**
* Coordinates document- and host-level DOM events for one rendered dropdown panel.
*
* @since 24.0.5
*/
class DropdownPanelDomEvents {
/**
* Creates an instance of DropdownPanelDomEvents.
*
* @param options - The options.
*
* @since 24.0.5
*/
constructor(options) {
this.options = options;
this.lastMousedownInside = false;
}
/**
* Starts dropdown DOM event monitoring.
*
* @since 24.0.5
*/
start() {
this.handleOutsideClick();
this.preventPanelMousedownFocus();
this.handleDocumentScroll();
this.handleSelectResize();
}
/**
* Subscribes to the configured outside-click event and closes when appropriate.
*
* @since 24.0.5
*/
handleOutsideClick() {
const { destroyRef, document, outsideClickEvent, zone } = this.options;
if (!document) {
return;
}
zone.runOutsideAngular(() => {
// Track where a click started because opening/focusing can move layout before click.
if (outsideClickEvent === 'click') {
fromEvent(document, 'mousedown', { capture: true })
.pipe(takeUntilDestroyed(destroyRef))
.subscribe((event) => (this.lastMousedownInside = isEventInside(event, this.options.select, this.options.dropdown)));
}
fromEvent(document, outsideClickEvent, { capture: true })
.pipe(takeUntilDestroyed(destroyRef))
.subscribe((event) => this.checkToClose(event));
});
}
/**
* Closes the dropdown when an event originated outside the select and panel.
*
* @param event - The DOM event to process.
*
* @since 24.0.5
*/
checkToClose(event) {
const pressStartedInside = this.lastMousedownInside;
this.lastMousedownInside = false;
if (pressStartedInside || isEventInside(event, this.options.select, this.options.dropdown)) {
return;
}
this.options.zone.run(this.options.onOutsideClick);
}
/**
* Prevents panel mouse interactions from stealing input focus.
*
* @since 24.0.5
*/
preventPanelMousedownFocus() {
const { destroyRef, dropdown, zone } = this.options;
zone.runOutsideAngular(() => {
fromEvent(dropdown, 'mousedown')
.pipe(takeUntilDestroyed(destroyRef))
.subscribe((event) => {
if (event.target.tagName !== 'INPUT') {
event.preventDefault();
}
});
});
}
/**
* Repositions the overlay when the document or an ancestor scrolls.
*
* @since 24.0.5
*/
handleDocumentScroll() {
const { destroyRef, document, dropdown, overlayRef, zone } = this.options;
if (!document || !overlayRef) {
return;
}
zone.runOutsideAngular(() => {
// Capture sees window and arbitrary ancestor scroll containers, even without cdkScrollable.
fromEvent(document, 'scroll', { capture: true, passive: true })
.pipe(takeUntilDestroyed(destroyRef), auditTime(0, EVENT_SCHEDULER))
.subscribe((event) => {
const target = event.target;
if (!target || !dropdown.contains(target)) {
overlayRef.updatePosition();
}
});
});
}
/**
* Keeps the overlay width and position synchronized with the select host.
*
* @since 24.0.5
*/
handleSelectResize() {
const { destroyRef, overlayRef, select, zone } = this.options;
if (!overlayRef || typeof ResizeObserver === 'undefined') {
return;
}
zone.runOutsideAngular(() => {
const observer = new ResizeObserver(() => {
overlayRef.updateSize({ width: select.getBoundingClientRect().width });
overlayRef.updatePosition();
});
observer.observe(select);
destroyRef.onDestroy(() => observer.disconnect());
});
}
}
const DROPDOWN_POSITION_BELOW = { originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top' };
const DROPDOWN_POSITION_ABOVE = { originX: 'start', originY: 'top', overlayX: 'start', overlayY: 'bottom' };
const DROPDOWN_POSITION_AFTER = { originX: 'end', originY: 'top', overlayX: 'start', overlayY: 'top' };
const DROPDOWN_POSITION_BEFORE = { originX: 'start', originY: 'top', overlayX: 'end', overlayY: 'top' };
const DROPDOWN_POSITIONS = {
// 'auto' prefers below and flips above when the panel does not fit the viewport;
// the fit check measures the rendered overlay, so header/footer templates and
// item count are accounted for (#2687, #2575)
auto: [DROPDOWN_POSITION_BELOW, DROPDOWN_POSITION_ABOVE],
bottom: [DROPDOWN_POSITION_BELOW],
top: [DROPDOWN_POSITION_ABOVE],
right: [DROPDOWN_POSITION_AFTER],
left: [DROPDOWN_POSITION_BEFORE],
};
const DROPDOWN_CSS_POSITIONS = ['top', 'right', 'bottom', 'left'];
/**
* Converts a CDK connection pair into the matching ng-select dropdown position.
*
* @param pair - The active CDK overlay connection pair.
* @returns The corresponding ng-select dropdown position.
*
* @since 24.0.5
*/
function connectionPairToDropdownPosition(pair) {
if (pair.originY === 'bottom' && pair.overlayY === 'top') {
return 'bottom';
}
if (pair.originY === 'top' && pair.overlayY === 'bottom') {
return 'top';
}
if (pair.originX === 'end' && pair.overlayX === 'start') {
return 'right';
}
if (pair.originX === 'start' && pair.overlayX === 'end') {
return 'left';
}
return 'bottom';
}
/**
* Calculates dropdown panel dimensions, virtual-scroll ranges, and scroll offsets.
*
* @since 3.0.0
*/
class NgDropdownPanelService {
constructor() {
this._dimensions = {
itemHeight: 0,
groupHeight: 0,
panelHeight: 0,
itemsPerViewport: 0,
};
}
/**
* Gets the currently measured panel dimensions.
*
* @returns The dimensions.
*
* @since 3.0.0
*/
get dimensions() {
return this._dimensions;
}
/**
* Calculates the virtual-scroll item range and spacer sizes.
*
* @param scrollPos - The scroll pos.
* @param itemsLength - The items length.
* @param buffer - The buffer.
* @param items - The options to process.
* @returns The rendered range and spacer sizes.
*
* @since 3.0.0
*/
calculateItems(scrollPos, itemsLength, buffer, items) {
const d = this._dimensions;
const useVariableHeights = !!items && d.groupHeight > 0 && d.groupHeight !== d.itemHeight;
if (useVariableHeights) {
return this._calculateItemsVariable(scrollPos, items, buffer);
}
const scrollHeight = d.itemHeight * itemsLength;
const scrollTop = Math.max(0, scrollPos);
const indexByScrollTop = (scrollTop / scrollHeight) * itemsLength;
let end = Math.min(itemsLength, Math.ceil(indexByScrollTop) + (d.itemsPerViewport + 1));
const maxStartEnd = end;
const maxStart = Math.max(0, maxStartEnd - d.itemsPerViewport);
let start = Math.min(maxStart, Math.floor(indexByScrollTop));
let topPadding = d.itemHeight * Math.ceil(start) - d.itemHeight * Math.min(start, buffer);
topPadding = !isNaN(topPadding) ? topPadding : 0;
start = !isNaN(start) ? start : -1;
end = !isNaN(end) ? end : -1;
start -= buffer;
start = Math.max(0, start);
end += buffer;
end = Math.min(itemsLength, end);
return {
topPadding,
scrollHeight,
start,
end,
};
}
/**
* Stores the measured item, group, and panel dimensions.
*
* @param itemHeight - The item height.
* @param panelHeight - The panel height.
* @param groupHeight - The group height.
*
* @since 3.0.0
*/
setDimensions(itemHeight, panelHeight, groupHeight = itemHeight) {
const effectiveItemHeight = itemHeight > 0 ? itemHeight : groupHeight;
const itemsPerViewport = effectiveItemHeight > 0 ? Math.max(1, Math.floor(panelHeight / effectiveItemHeight)) : 0;
this._dimensions = {
itemHeight,
groupHeight: groupHeight > 0 ? groupHeight : itemHeight,
panelHeight,
itemsPerViewport,
};
}
/**
* Returns the measured height for an option or group.
*
* @param item - The option to process.
* @returns The row height in pixels.
*
* @since 23.9.0
*/
getItemHeight(item) {
const d = this._dimensions;
if (item?.children) {
return d.groupHeight || d.itemHeight;
}
return d.itemHeight || d.groupHeight;
}
/**
* Calculates an item’s vertical offset within the full option list.
*
* @param items - The options to process.
* @param index - The index.
* @returns The vertical offset in pixels.
*
* @since 23.9.0
*/
getItemOffset(items, index) {
let offset = 0;
const end = Math.max(0, Math.min(index, items.length));
for (let i = 0; i < end; i++) {
offset += this.getItemHeight(items[i]);
}
return offset;
}
/**
* Calculates the total virtual-scroll content height.
*
* @param items - The options to process.
* @returns The total scroll height in pixels.
*
* @since 23.9.0
*/
getScrollHeight(items) {
return this.getItemOffset(items, items.length);
}
/**
* Calculates the scroll position needed to reveal an item.
*
* @param itemTop - The item top.
* @param itemHeight - The item height.
* @param lastScroll - The last scroll.
*
* @since 3.0.0
*/
getScrollTo(itemTop, itemHeight, lastScroll) {
const { panelHeight } = this.dimensions;
const itemBottom = itemTop + itemHeight;
const top = lastScroll;
const bottom = top + panelHeight;
if (panelHeight >= itemBottom && lastScroll === itemTop) {
return null;
}
if (itemBottom > bottom) {
return top + itemBottom - bottom;
}
else if (itemTop <= top) {
return itemTop;
}
return null;
}
/**
* Calculates a virtual-scroll range for variable-height rows.
*
* @param scrollPos - The scroll pos.
* @param items - The options to process.
* @param buffer - The buffer.
* @returns The calculate items variable result.
*
* @since 23.9.0
*/
_calculateItemsVariable(scrollPos, items, buffer) {
const { panelHeight } = this._dimensions;
const itemsLength = items.length;
const scrollHeight = this.getScrollHeight(items);
const scrollTop = Math.max(0, scrollPos);
let accumulated = 0;
let start = 0;
for (let i = 0; i < itemsLength; i++) {
const height = this.getItemHeight(items[i]);
if (accumulated + height > scrollTop) {
start = i;
break;
}
accumulated += height;
start = i + 1;
}
let end = start;
let visibleHeight = 0;
while (end < itemsLength && visibleHeight < panelHeight) {
visibleHeight += this.getItemHeight(items[end]);
end++;
}
// Match the uniform path's +1 viewport slack so the last partially-visible row is included.
end = Math.min(itemsLength, end + 1);
const bufferedStart = Math.max(0, start - buffer);
const bufferedEnd = Math.min(itemsLength, end + buffer);
const topPadding = this.getItemOffset(items, bufferedStart);
return {
topPadding,
scrollHeight,
start: bufferedStart,
end: bufferedEnd,
};
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: NgDropdownPanelService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: NgDropdownPanelService }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: NgDropdownPanelService, decorators: [{
type: Injectable
}] });
/**
* Determines whether a value is neither `undefined` nor `null`.
*
* @param value - The value to process.
*
* @since 3.0.0
*/
function isDefined(value) {
return value !== undefined && value !== null;
}
/**
* Determines whether a value is a non-null object.
*
* @param value - The value to process.
*
* @since 3.0.0
*/
function isObject(value) {
return typeof value === 'object' && isDefined(value);
}
/**
* Determines whether a value is promise-like.
*
* @param value - The value to process.
*
* @since 3.0.0
*/
function isPromise(value) {
return value instanceof Promise;
}
/**
* Determines whether a value is a function.
*
* @param value - The value to process.
*
* @since 3.0.0
*/
function isFunction(value) {
return value instanceof Function;
}
const SCROLL_SCHEDULER = typeof requestAnimationFrame !== 'undefined' ? animationFrameScheduler : asapScheduler;
/**
* Renders, positions, and virtualizes the dropdown option panel.
*
* @since 3.0.0
*/
class NgDropdownPanelComponent {
constructor() {
this.items = input([], /* @ts-ignore */
...(ngDevMode ? [{ debugName: "items" }] : /* istanbul ignore next */ []));
this.showAddTag = input(false, { ...(ngDevMode ? { debugName: "showAddTag" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
this.markedItem = input(undefined, /* @ts-ignore */
...(ngDevMode ? [{ debugName: "markedItem" }] : /* istanbul ignore next */ []));
this.position = input('auto', /* @ts-ignore */
...(ngDevMode ? [{ debugName: "position" }] : /* istanbul ignore next */ []));
this.bufferAmount = input(undefined, /* @ts-ignore */
...(ngDevMode ? [{ debugName: "bufferAmount" }] : /* istanbul ignore next */ []));
this.virtualScroll = input(false, { ...(ngDevMode ? { debugName: "virtualScroll" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
this.headerTemplate = input(undefined, /* @ts-ignore */
...(ngDevMode ? [{ debugName: "headerTemplate" }] : /* istanbul ignore next */ []));
this.footerTemplate = input(undefined, /* @ts-ignore */
...(ngDevMode ? [{ debugName: "footerTemplate" }] : /* istanbul ignore next */ []));
this.filterValue = input(null, /* @ts-ignore */
...(ngDevMode ? [{ debugName: "filterValue" }] : /* istanbul ignore next */ []));
this.ariaLabelDropdown = input(null, /* @ts-ignore */
...(ngDevMode ? [{ debugName: "ariaLabelDropdown" }] : /* istanbul ignore next */ []));
this.listboxId = input(null, /* @ts-ignore */
...(ngDevMode ? [{ debugName: "listboxId" }] : /* istanbul ignore next */ []));
/**
* Which DOM event to listen to for outside click detection
*/
this.outsideClickEvent = input('click', /* @ts-ignore */
...(ngDevMode ? [{ debugName: "outsideClickEvent" }] : /* istanbul ignore next */ []));
/** @deprecated Has no effect: the CDK overlay renders in the native Popover API top layer automatically in supporting browsers. Will be removed in a future major version. */
this.popover = input(false, { ...(ngDevMode ? { debugName: "popover" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
/** Overlay hosting this panel. Used to request repositioning when the rendered content changes. */
this.overlayRef = input(null, /* @ts-ignore */
...(ngDevMode ? [{ debugName: "overlayRef" }] : /* istanbul ignore next */ []));
/** Host `ng-select` element. The panel's DOM lives in the overlay, so the host cannot be derived from the DOM tree. */
this.selectElement = input(undefined, /* @ts-ignore */
...(ngDevMode ? [{ debugName: "selectElement" }] : /* istanbul ignore next */ []));
this.update = output();
this.scroll = output();
this.scrollToEnd = output();
this.outsideClick = output();
this._renderer = inject(Renderer2);
this._zone = inject(NgZone);
this._panelService = inject(NgDropdownPanelService);
this._document = inject(DOCUMENT, { optional: true });
this._destroyRef = inject(DestroyRef);
this._dropdown = inject((ElementRef)).nativeElement;
this.contentElementRef = viewChild('content', { ...(ngDevMode ? { debugName: "contentElementRef" } : /* istanbul ignore next */ {}), read: ElementRef });
this.scrollElementRef = viewChild('scroll', { ...(ngDevMode ? { debugName: "scrollElementRef" } : /* istanbul ignore next */ {}), read: ElementRef });
this.paddingElementRef = viewChild('padding', { ...(ngDevMode ? { debugName: "paddingElementRef" } : /* istanbul ignore next */ {}), read: ElementRef });
this._virtualPadding = computed(() => this.paddingElementRef()?.nativeElement, /* @ts-ignore */
...(ngDevMode ? [{ debugName: "_virtualPadding" }] : /* istanbul ignore next */ []));
this._scrollablePanel = computed(() => this.scrollElementRef()?.nativeElement, /* @ts-ignore */
...(ngDevMode ? [{ debugName: "_scrollablePanel" }] : /* istanbul ignore next */ []));
this._contentPanel = computed(() => this.contentElementRef()?.nativeElement, /* @ts-ignore */
...(ngDevMode ? [{ debugName: "_contentPanel" }] : /* istanbul ignore next */ []));
this._scrollToEndFired = false;
this._updateScrollHeight = false;
this._lastScrollPosition = 0;
}
/**
* Gets the panel position selected by the overlay strategy.
*
* @returns The current position.
*
* @since 3.0.0
*/
get currentPosition() {
return this._currentPosition;
}
/**
* Gets the number of rows available to the panel.
*
* @returns The items length.
*
* @since 3.0.0
*/
get itemsLength() {
return this._itemsLength;
}
/**
* Updates the cached item count and resets dependent virtual-scroll state.
*
* @param value - The value to process.
*
* @since 3.0.0
*/
set itemsLength(value) {
if (value !== this._itemsLength) {
this._itemsLength = value;
this._onItemsLengthChanged();
}
}
/**
* Gets the rendered range’s starting vertical offset.
*
* @returns The start offset.
*
* @since 3.0.0
*/
get _startOffset() {
if (this.markedItem()) {
const { panelHeight } = this._panelService.dimensions;
const offset = this._panelService.getItemOffset(this.items(), this.markedItem().index);
return panelHeight > offset ? 0 : offset;
}
return 0;
}
/**
* Initializes the instance after Angular has assigned its inputs.
*
* @since 3.0.0
*/
ngOnInit() {
this._select = this.selectElement() ?? this._dropdown.parentElement;
this._handleScroll();
new DropdownPanelDomEvents({
destroyRef: this._destroyRef,
document: this._document,
dropdown: this._dropdown,
onOutsideClick: () => this.outsideClick.emit(),
outsideClickEvent: this.outsideClickEvent() ?? 'click',
overlayRef: this.overlayRef(),
select: this._select,
zone: this._zone,
}).start();
this._subscribeOverlayPosition();
}
/**
* Responds to Angular input changes.
*
* @param changes - The changed Angular inputs.
*
* @since 3.0.0
*/
ngOnChanges(changes) {
if (changes.items) {
const change = changes.items;
this._onItemsOrShowAddTagChange(change.currentValue, this.showAddTag(), change.firstChange);
}
if (changes.showAddTag) {
const change = changes.showAddTag;
this._onItemsOrShowAddTagChange(this.items(), change.currentValue, change.firstChange);
}
}
/**
* Scrolls until the requested option is visible.
*
* @param option - The option to process.
* @param startFromOption - The start from option.
*
* @since 3.0.0
*/
scrollTo(option, startFromOption = false) {
if (!option) {
return;
}
const index = this.items().indexOf(option);
if (index < 0 || index >= this.itemsLength) {
return;
}
let scrollTo;
if (this.virtualScroll()) {
const items = this.items();
const itemHeight = this._panelService.getItemHeight(option);
const itemTop = this._panelService.getItemOffset(items, index);
scrollTo = this._panelService.getScrollTo(itemTop, itemHeight, this._lastScrollPosition);
}
else {
const item = this._dropdown.querySelector(`#${option.htmlId}`);
if (!item) {
return;
}
const lastScroll = startFromOption ? item.offsetTop : this._lastScrollPosition;
scrollTo = this._panelService.getScrollTo(item.offsetTop, item.clientHeight, lastScroll);
}
if (isDefined(scrollTo)) {
this._scrollablePanel().scrollTop = scrollTo;
// Programmatic scrollTop may not emit `scroll` in time; sync the range now (#2744)
if (this.virtualScroll()) {
this._onContentScrolled(scrollTo);
}
else {
this._lastScrollPosition = scrollTo;
}
}
}
/**
* Scrolls to the add-tag row.
*
* @since 3.0.0
*/
scrollToTag() {
const panel = this._scrollablePanel();
panel.scrollTop = panel.scrollHeight - panel.clientHeight;
}
/**
* Requests that the overlay strategy recalculate the panel position.
*
* @since 3.0.0
*/
adjustPosition() {
this.overlayRef()?.updatePosition();
}
/**
* Applies the position chosen by the overlay's position strategy to the panel and host
* as `ng-select-top` / `ng-select-bottom` (and `-left` / `-right`) classes, which the
* shipped themes use for borders, radius and spacing.
*
* @since 23.7.0
*/
_setCurrentPosition(position) {
this._currentPosition = position;
if (DROPDOWN_CSS_POSITIONS.includes(position)) {
this._updateDropdownClass(position);
}
else {
this._updateDropdownClass('bottom');
}
}
/**
* Positions the freshly rendered panel. The overlay measures the real DOM, so header and
* footer templates and the actual item count are part of the auto placement decision
* (#2575). Only then is the panel made visible to avoid a flash at a stale position.
*
* @since 23.7.0
*/
_positionDropdown() {
const overlayRef = this.overlayRef();
if (overlayRef) {
// Apply position and emit positionChanges before the panel is visible
overlayRef.updatePosition();
}
else if (!isDefined(this._currentPosition)) {
// Standalone usage without an overlay: reflect the configured side directly
this._setCurrentPosition(this.position() === 'auto' ? 'bottom' : this.position());
}
this._dropdown.style.opacity = '1';
}
/**
* Subscribes to CDK overlay position changes.
*
* @since 23.7.0
*/
_subscribeOverlayPosition() {
const strategy = this.overlayRef()?.getConfig().positionStrategy;
if (!(strategy instanceof FlexibleConnectedPositionStrategy)) {
return;
}
strategy.positionChanges.pipe(takeUntilDestroyed(this._destroyRef)).subscribe((change) => {
this._setCurrentPosition(connectionPairToDropdownPosition(change.connectionPair));
});
}
/**
* Updates the position-specific CSS class on the panel.
*
* @param currentPosition - The current position.
*
* @since 7.4.0
*/
_updateDropdownClass(currentPosition) {
DROPDOWN_CSS_POSITIONS.forEach((position) => {
const REMOVE_CSS_CLASS = `ng-select-${position}`;
this._renderer.removeClass(this._dropdown, REMOVE_CSS_CLASS);
this._renderer.removeClass(this._select, REMOVE_CSS_CLASS);
});
const ADD_CSS_CLASS = `ng-select-${currentPosition}`;
this._renderer.addClass(this._dropdown, ADD_CSS_CLASS);
this._renderer.addClass(this._select, ADD_CSS_CLASS);
}
/**
* Subscribes to panel scrolling outside Angular change detection.
*
* @since 3.0.0
*/
_handleScroll() {
this._zone.runOutsideAngular(() => {
const scrollablePanel = this._scrollablePanel();
if (!scrollablePanel) {
return;
}
fromEvent(scrollablePanel, 'scroll')
.pipe(takeUntilDestroyed(this._destroyRef), auditTime(0, SCROLL_SCHEDULER))
.subscribe(() => {
this._onContentScrolled(scrollablePanel.scrollTop);
});
});
}
/**
* Refreshes rendered items when options or add-tag visibility changes.
*
* @param items - The options to process.
* @param showAddTag - The show add tag.
* @param firstChange - The first change.
*
* @since 20.6.2
*/
_onItemsOrShowAddTagChange(items = [], showAddTag, firstChange) {
this._scrollToEndFired = false;
this.itemsLength = items.length;
if (showAddTag && items.length) {
this.itemsLength++;
}
if (this.virtualScroll()) {
this._updateItemsRange(firstChange);
}
else {
this._setVirtualHeight();
this._updateItems(firstChange);
}
}
/**
* Rebuilds the rendered option collection and virtual-scroll measurements.
*
* @param firstChange - The first change.
*
* @since 3.0.0
*/
_updateItems(firstChange) {
this.update.emit(this.items());
this._zone.runOutsideAngular(() => {
Promise.resolve().then(() => {
// Panel may have opened empty; refresh height for scrollTo math (#2744)
this._syncPanelHeightFromDom();
if (!firstChange) {
// Re-anchor so a top-placed panel grows upward and `auto` can flip (#2092)
this.overlayRef()?.updatePosition();
return;
}
this._positionDropdown();
this.scrollTo(this.markedItem(), firstChange);
});
});
}
/**
* Recalculates the rendered virtual-scroll range.
*
* @param firstChange - The first change.
*
* @since 3.0.0
*/
_updateItemsRange(firstChange) {
this._zone.runOutsideAngular(() => {
this._measureDimensions().then(() => {
const scrollTop = firstChange ? this._startOffset : (this._scrollablePanel()?.scrollTop ?? 0);
if (!firstChange) {
// Items changed at an unchanged scrollTop; bypass the same-position guard (#2880)
this._lastScrollPosition = -1;
}
this._renderItemsRange(scrollTop);
if (!firstChange) {
this._lastScrollPosition = scrollTop;
}
// Sync panelHeight after the first paint; re-render if viewport capacity grew (#2744)
const itemsPerViewportBefore = this._panelService.dimensions.itemsPerViewport;
this._syncPanelHeightFromDom();
if (this._panelService.dimensions.itemsPerViewport !== itemsPerViewportBefore) {
const currentScrollTop = this._scrollablePanel()?.scrollTop ?? scrollTop;
this._lastScrollPosition = -1;
this._renderItemsRange(currentScrollTop);
this._lastScrollPosition = currentScrollTop;
}
if (firstChange) {
this._positionDropdown();
}
else {
this.overlayRef()?.updatePosition();
}
});
});
}
/**
* Updates cached panelHeight from the live scrollport, preserving row-height measurements.
*
* @since 23.9.0
*/
_syncPanelHeightFromDom() {
const panel = this._scrollablePanel();
if (!panel) {
return;
}
const panelHeight = panel.clientHeight;
if (panelHeight <= 0) {
return;
}
const { itemHeight, groupHeight } = this._panelService.dimensions;
this._panelService.setDimensions(itemHeight, panelHeight, groupHeight);
}
/**
* Updates virtual-scroll state for a new scroll position.
*
* @param scrollTop - The scroll top.
*
* @since 3.0.0
*/
_onContentScrolled(scrollTop) {
if (this.virtualScroll()) {
this._renderItemsRange(scrollTop);
}
this._lastScrollPosition = scrollTop;
this._fireScrollToEnd(scrollTop);
}
/**
* Updates the virtual-scroll spacer height.
*
* @param height - The height.
*
* @since 3.0.0
*/
_updateVirtualHeight(height) {
if (this._updateScrollHeight) {
this._virtualPadding().style.height = `${height}px`;
this._updateScrollHeight = false;
}
}
/**
* Measures and applies the virtual-scroll spacer height.
*
* @since 5.0.1
*/
_setVirtualHeight() {
if (!this._virtualPadding()) {
return;
}
this._virtualPadding().style.height = `0px`;
}
/**
* Resets virtual-scroll state after the item count changes.
*
* @since 3.0.0
*/
_onItemsLengthChanged() {
this._updateScrollHeight = true;
}
/**
* Renders the virtual-scroll range for the current scroll position.
*
* @param scrollTop - The scroll top.
*
* @since 3.0.0
*/
_renderItemsRange(scrollTop = null) {
if (scrollTop && this._lastScrollPosition === scrollTop) {
return;
}
scrollTop = scrollTop || this._scrollablePanel().scrollTop;
const range = this._panelService.calculateItems(scrollTop, this.itemsLength, this.bufferAmount(), this.items());
this._updateVirtualHeight(range.scrollHeight);
this._contentPanel().style.transform = `translateY(${range.topPadding}px)`;
// Outputs must stay template-bound: the template listener schedules CD under zoneless
this._zone.run(() => {
this.update.emit(this.items().slice(range.start, range.end));
this.scroll.emit({ start: range.start, end: range.end });
});
if (isDefined(scrollTop) && this._lastScrollPosition === 0) {
this._scrollablePanel().scrollTop = scrollTop;
this._lastScrollPosition = scrollTop;
}
}
/**
* Measures option, group, and viewport dimensions after rendering.
*
* @returns The measure dimensions result.
*
* @since 3.0.0
*/
_measureDimensions() {
if (this._panelService.dimensions.itemHeight > 0 || this.itemsLength === 0) {
return Promise.resolve(this._panelService.dimensions);
}
const items = this.items();
const firstGroup = items.find((item) => !!item.children);
const firstOption = items.find((item) => !item.children);
const toMeasure = [firstGroup, firstOption].filter((item) => !!item);
if (toMeasure.length === 0) {
return Promise.resolve(this._panelService.dimensions);
}
// Emitted items render in the same CD pass; measure both a group and an option (#2762)
this._zone.run(() => this.update.emit(toMeasure));
return Promise.resolve()
.then(() => this._readMeasuredDimensions(items, firstOption, firstGroup))
.then((dims) => {
if (dims.itemHeight > 0) {
return dims;
}
// Options may not have painted yet; retry once next frame (#2744)
return new Promise((resolve) => {
requestAnimationFrame(() => {
this._zone.run(() => this.update.emit(toMeasure));
Promise.resolve().then(() => resolve(this._readMeasuredDimensions(items, firstOption, firstGroup)));
});
});
});
}
/**
* Reads option, group, and viewport dimensions from rendered elements.
*
* @param items - The options to process.
* @param firstOption - The first option.
* @param firstGroup - The first group.
* @returns The read measured dimensions result.
*
* @since 23.9.0
*/
_readMeasuredDimensions(items, firstOption, firstGroup) {
const optionEl = firstOption ? this._dropdown.querySelector(`#${firstOption.htmlId}`) : null;
const groupEl = firstGroup ? this._dropdown.querySelector(`#${firstGroup.htmlId}`) : null;
if (!optionEl && !groupEl) {
return this._panelService.dimensions;
}
const measuredOptionHeight = optionEl?.offsetHeight ?? 0;
const measuredGroupHeight = groupEl?.offsetHeight ?? 0;
const optionHeight = measuredOptionHeight || measuredGroupHeight;
const groupHeight = measuredGroupHeight || measuredOptionHeight;
const panelHeight = this._scrollablePanel().clientHeight;
this._panelService.setDimensions(optionHeight, panelHeight, groupHeight);
this._virtualPadding().style.height = `${this._panelService.getScrollHeight(items)}px`;
return this._panelService.dimensions;
}
/**
* Emits `scrollToEnd` once the viewport reaches the end of the content.
*
* @param scrollTop - The scroll top.
*
* @since 3.0.0
*/
_fireScrollToEnd(scrollTop) {
if (this._scrollToEndFired || scrollTop === 0) {
return;
}
const padding = this.virtualScroll() ? this._virtualPadding() : this._contentPanel();
if (scrollTop + this._dropdown.clientHeight >= padding.clientHeight - 1) {
this._zone.run(() => this.scrollToEnd.emit());
this._scrollToEndFired = true;
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: NgDropdownPanelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.4", type: NgDropdownPanelComponent, isStandalone: true, selector: "ng-dropdown-panel", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: false, transformFunction: null }, showAddTag: { classPropertyName: "showAddTag", publicName: "showAddTag", isSignal: true, isRequired: false, transformFunction: null }, markedItem: { classPropertyName: "markedItem", publicName: "markedItem", isSignal: true, isRequired: false, transformFunction: null }, position: { classPropertyName: "position", publicName: "position", isSignal: true, isRequired: false, transformFunction: null }, bufferAmount: { classPropertyName: "bufferAmount", publicName: "bufferAmount", isSignal: true, isRequired: false, transformFunction: null }, virtualScroll: { classPropertyName: "virtualScroll", publicName: "virtualScroll", isSignal: true, isRequired: false, transformFunction: null }, headerTemplate: { classPropertyName: "headerTemplate", publicName: "headerTemplate", isSignal: true, isRequired: false, transformFunction: null }, footerTemplate: { classPropertyName: "footerTemplate", publicName: "footerTemplate", isSignal: true, isRequired: false, transformFunction: null }, filterValue: { classPropertyName: "filterValue", publicName: "filterValue", isSignal: true, isRequired: false, transformFunction: null }, ariaLabelDropdown: { classPropertyName: "ariaLabelDropdown", publicName: "ariaLabelDropdown", isSignal: true, isRequired: false, transformFunction: null }, listboxId: { classPropertyName: "listboxId", publicName: "listboxId", isSignal: true, isRequired: false, transformFunction: null }, outsideClickEvent: { classPropertyName: "outsideClickEvent", publicName: "outsideClickEvent", isSignal: true, isRequired: false, transformFunction: null }, popover: { classPropertyName: "popover", publicName: "popover", isSignal: true, isRequired: false, transformFunction: null }, overlayRef: { classPropertyName: "overlayRef", publicName: "overlayRef", isSignal: true, isRequired: false, transformFunction: null }, selectElement: { classPropertyName: "selectElement", publicName: "selectElement", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { update: "update", scroll: "scroll", scrollToEnd: "scrollToEnd", outsideClick: "outsideClick" }, viewQueries: [{ propertyName: "contentElementRef", first: true, predicate: ["content"], descendants: true, read: ElementRef, isSignal: true }, { propertyName: "scrollElementRef", first: true, predicate: ["scroll"], descendants: true, read: ElementRef, isSignal: true }, { propertyName: "paddingElementRef", first: true, predicate: ["padding"], descendants: true, read: ElementRef, isSignal: true }], usesOnChanges: true, ngImport: i0, template: `
@if (headerTemplate()) {
<div class="ng-dropdown-header">
<ng-container [ngTemplateOutlet]="headerTemplate()" [ngTemplateOutletContext]="{ searchTerm: filterValue() }" />
</div>
}
<div #scroll role="listbox" class="ng-dropdown-panel-items scroll-host" [attr.id]="listboxId()" [attr.aria-label]="ariaLabelDropdown()">
<div #padding [class.total-padding]="virtualScroll()"></div>
<div #content [class.scrollable-content]="virtualScroll() && items().length">
<ng-content />
</div>
</div>
@if (footerTemplate()) {
<div class="ng-dropdown-footer">
<ng-container [ngTemplateOutlet]="footerTemplate()" [ngTemplateOutletContext]="{ searchTerm: filterValue() }" />
</div>
}
`, isInline: true, dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: NgDropdownPanelComponent, decorators: [{
type: Component,
args: [{
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None,
selector: 'ng-dropdown-panel',
template: `
@if (headerTemplate()) {
<div class="ng-dropdown-header">
<ng-container [ngTemplateOutlet]="headerTemplate()" [ngTemplateOutletContext]="{ searchTerm: filterValue() }" />
</div>
}
<div #scroll role="listbox" class="ng-dropdown-panel-items scroll-host" [attr.id]="listboxId()" [attr.aria-label]="ariaLabelDropdown()">
<div #padding [class.total-padding]="virtualScroll()"></div>
<div #content [class.scrollable-content]="virtualScroll() && items().length">
<ng-content />
</div>
</div>
@if (footerTemplate()) {
<div class="ng-dropdown-footer">
<ng-container [ngTemplateOutlet]="footerTemplate()" [ngTemplateOutletContext]="{ searchTerm: filterValue() }" />
</div>
}
`,
imports: [NgTemplateOutlet],
}]
}], propDecorators: { items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: false }] }], showAddTag: [{ type: i0.Input, args: [{ isSignal: true, alias: "showAddTag", required: false }] }], markedItem: [{ type: i0.Input, args: [{ isSignal: true, alias: "markedItem", required: false }] }], position: [{ type: i0.Input, args: [{ isSignal: true, alias: "position", required: false }] }], bufferAmount: [{ type: i0.Input, args: [{ isSignal: true, alias: "bufferAmount", required: false }] }], virtualScroll: [{ type: i0.Input, args: [{ isSignal: true, alias: "virtualScroll", required: false }] }], headerTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "headerTemplate", required: false }] }], footerTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "footerTemplate", required: false }] }], filterValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "filterValue", required: false }] }], ariaLabelDropdown: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabelDropdown", required: false }] }], listboxId: [{ type: i0.Input, args: [{ isSignal: true, alias: "listboxId", required: false }] }], outsideClickEvent: [{ type: i0.Input, args: [{ isSignal: true, alias: "outsideClickEvent", required: false }] }], popover: [{ type: i0.Input, args: [{ isSignal: true, alias: "popover", required: false }] }], overlayRef: [{ type: i0.Input, args: [{ isSignal: true, alias: "overlayRef", required: false }] }], selectElement: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectElement", required: false }] }], update: [{ type: i0.Output, args: ["update"] }], scroll: [{ type: i0.Output, args: ["scroll"] }], scrollToEnd: [{ type: i0.Output, args: ["scrollToEnd"] }], outsideClick: [{ type: i0.Output, args: ["outsideClick"] }], contentElementRef: [{ type: i0.ViewChild, args: ['content', { ...{ read: ElementRef }, isSignal: true }] }], scrollElementRef: [{ type: i0.ViewChild, args: ['scroll', { ...{ read: ElementRef }, isSignal: true }] }], paddingElementRef: [{ type: i0.ViewChild, args: ['padding', { ...{ read: ElementRef }, isSignal: true }] }] } });
/**
* Declares an option for ng-select declared via HTML. Use the value input to bind the option's value and the element content as its label.
*
* @since 3.0.0
*/
class NgOptionComponent {
/**
* Creates an instance of NgOptionComponent.
*
* @since 3.0.0
*/
constructor() {
/** Value bound to the option. The projected element content is used as the option's label. */
this.value = input(/* @ts-ignore */
...(ngDevMode ? [undefined, { debugName: "value" }] : /* istanbul ignore next */ []));
/** Whether the option is disabled and cannot be selected. */
this.disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
this.elementRef = inject((ElementRef));
this.label = signal('', /* @ts-ignore */
...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
this.classes = signal('', /* @ts-ignore */
...(ngDevMode ? [{ debugName: "classes" }] : /* istanbul ignore next */ []));
/** True when this component's inputs are initialized (after first change detection). */
this.isInitialized = signal(false, /* @ts-ignore */
...(ngDevMode ? [{ debugName: "isInitialized" }] : /* istanbul ignore next */ []));
afterEveryRender(() => {
const element = this.elementRef.nativeElement;
// textContent (not innerHTML): default labels are plain text, like Material viewValue.
const currentLabel = (element.textContent || '').trim();
if (currentLabel !== this.label()) {
this.label.set(currentLabel);
}
const currentClasses = Array.from(element.classList)
.filter((className) => className !== 'ng-star-inserted')
.join(' ');
if (currentClasses !== this.classes()) {
this.classes.set(currentClasses);
}
});
}
/**
* Initializes the instance after Angular has assigned its inputs.
*
* @since 21.5.2
*/
ngOnInit() {
this.isInitialized.set(true);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: NgOptionComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.1.4", type: NgOptionComponent, isStandalone: true, selector: "ng-option", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `<ng-content />`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: NgOptionComponent, decorators: [{
type: Component,
args: [{
selector: 'ng-option',
standalone: true,
changeDetection: ChangeDetectionStrategy.OnPush,
template: `<ng-content />`,
}]
}], ctorParameters: () => [], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }] } });
/**
* Provides an overlay container that preserves DOM containment when `appendTo` is configured.
*
* @since 24.0.5
*/
class NgSelectAppendToOverlayContainer extends OverlayContainer {
/**
* Creates an instance of NgSelectAppendToOverlayContainer.
*
* @param appendToHost - The append to host.
*
* @since 24.0.5
*/
constructor(appendToHost) {
super();
this.appendToHost = appendToHost;
}
/**
* Creates the CDK overlay container inside the configured append target.
*
* @since 24.0.5
*/
_createContainer() {
super._createContainer();
this.appendToHost.appendChild(this._containerElement);
}
}
/**
* Owns the CDK overlay resources used by one ng-select instance.
*
* @since 24.0.5
*/
class DropdownOverlayManager {
/**
* Creates an instance of DropdownOverlayManager.
*
* @param injector - The injector.
* @param document - The document.
* @param selectElement - The select element.
*
* @since 24.0.5
*/
constructor(injector, document, selectElement) {
this.injector = injector;
this.document = document;
this.selectElement = selectElement;
this.overlayRef = null;
this.positionStrategy = null;
this.portal = null;
this.overlayAppendTo = null;
this.overlayAppendToHost = null;
this.appendToContainer = null;
}
/**
* Opens the dropdown panel.
*
* @param request - The request.
* @returns The attached overlay reference.
*
* @since 24.0.5
*/
open(request) {
const overlayRef = this.ensureOverlay(request.origin, request.appendTo);
this.positionStrategy.withPositions([...(DROPDOWN_POSITIONS[request.position] ?? DROPDOWN_POSITIONS.auto)]);
// Direction is snapshotted per open, matching the previous appendTo behavior.
overlayRef.setDirection(this.document.documentElement?.dir === 'rtl' ? 'rtl' : 'ltr');
overlayRef.updateSize({ width: request.origin.getBoundingClientRect().width });
// The parent binds this reference into the portal content, so publish it before attachment.
request.beforeAttach?.(overlayRef);
if (overlayRef.hasAttached()) {
overlayRef.updatePosition();
return overlayRef;
}
this.portal ??= new TemplatePortal(request.template, request.viewContainer);
overlayRef.attach(this.portal);
return overlayRef;
}
/**
* Closes the dropdown panel.
*
* @since 24.0.5
*/
close() {
this.overlayRef?.detach();
}
/**
* Disposes the owned overlay resources.
*
* @since 24.0.5
*/
destroy() {
this.overlayRef?.dispose();
this.overlayRef = null;
this.positionStrategy = null;
this.overlayAppendToHost = null;
this.appendToContainer?.ngOnDestroy();
this.appendToContainer = null;
}
/**
* Returns the existing overlay or creates one for the requested host.
*
* @param origin - The origin.
* @param appendTo - The append to.
* @returns The reusable overlay reference.
*
* @since 24.0.5
*/
ensureOverlay(origin, appendTo) {
// Resolve on each open so a selector keeps working when its matching element is replaced.
const appendToHost = appendTo ? this.resolveAppendToHost(appendTo) : null;
if (this.overlayRef && (appendTo !== this.overlayAppendTo || appendToHost !== this.overlayAppendToHost)) {
this.destroy();
}
if (!this.overlayRef) {
let injector = this.injector;
if (appendToHost) {
this.appendToContainer = runInInjectionContext(this.injector, () => new NgSelectAppendToOverlayContainer(appendToHost));
injector = Injector.create({
parent: this.injector,
providers: [{ provide: OverlayContainer, useValue: this.appendToContainer }],
});
}
this.positionStrategy = createFlexibleConnectedPositionStrategy(injector, origin).withFlexibleDimensions(false).withPush(false);
if (appendToHost) {
// Supporting browsers paint in the Popover API top layer. The host still controls
// DOM containment for ancestor-scoped styles and focus enclosure.
this.positionStrategy.withPopoverLocation({ type: 'parent', element: appendToHost });
}
this.overlayAppendTo = appendTo;
this.overlayAppendToHost = appendToHost;
this.overlayRef = createOverlayRef(injector, {
positionStrategy: this.positionStrategy,
// The panel handles ancestor scrolling, including containers without cdkScrollable.
scrollStrategy: createNoopScrollStrategy(),
// No panel animation needs to delay synchronous DOM removal.
disableAnimations: true,
});
}
return this.overlayRef;
}
/**
* Resolves the configured `appendTo` selector within the select root.
*
* @param selector - The CSS selector to resolve.
* @returns The element matching the selector.
*
* @since 24.0.5
*/
resolveAppendToHost(selector) {
const root = this.selectElement.getRootNode();
const scope = typeof ShadowRoot !== 'undefined' && root instanceof ShadowRoot ? root : this.document;
const host = scope.querySelector(selector);
if (!host) {
throw new Error(`appendTo selector ${selector} did not found any parent element`);
}
return host;
}
}
const INTERNAL_HOST_CLASSES = new Set([
'ng-select',
'ng-select-single',
'ng-select-multiple',
'ng-select-typeahead',
'ng-select-taggable',
'ng-select-searchable',
'ng-select-clearable',
'ng-select-opened',
'ng-select-filtered',
'ng-select-disabled',
'ng-select-focused',
'ng-select-top',
'ng-select-bottom',
'ng-select-left',
'ng-select-right',
]);
const ANGULAR_MANAGED_HOST_CLASSES = new Set([
'ng-star-inserted',
'ng-untouched',
'ng-touched',
'ng-pristine',
'ng-dirty',
'ng-valid',
'ng-invalid',
'ng-pending',
'ng-submitted',
]);
/**
* Reads consumer-provided classes from the ng-select host while excluding internal Angular and component state classes.
*
* @param element - The ng-select host element.
* @returns A space-separated list of consumer-provided classes.
*
* @since 24.0.5
*/
function readConsumerHostClasses(element) {
return Array.from(element.classList)
.filter((className) => !INTERNAL_HOST_CLASSES.has(className) && !ANGULAR_MANAGED_HOST_CLASSES.has(className))
.join(' ');
}
/**
* Normalizes and merges supported class value formats into a unique class string.
*
* @param values - Class values to normalize and merge.
* @returns A space-separated list of unique class names.
*
* @since 24.0.5
*/
function mergeClassValues(...values) {
const classes = new Set();
for (const value of values) {
collectClasses(classes, value);
}
return Array.from(classes).join(' ');
}
/**
* Adds normalized class values to the target set.
*
* @param target - The target.
* @param value - The value to process.
*
* @since 24.0.5
*/
function collectClasses(target, value) {
if (!value) {
return;
}
if (typeof value === 'string') {
collectClassString(target, value);
return;
}
if (Array.isArray(value) || value instanceof Set) {
for (const className of value) {
collectClassString(target, className);
}
return;
}
for (const [classNames, enabled] of Object.entries(value)) {
if (enabled) {
collectClassString(target, classNames);
}
}
}
/**
* Splits a class string and adds its non-empty names to the target set.
*
* @param target - The target.
* @param value - The value to process.
*
* @since 24.0.5
*/
function collectClassString(target, value) {
for (const className of value.split(/\s+/)) {
if (className) {
target.add(className);
}
}
}
/**
* Creates a unique identifier for an ng-select element.
*
* @since 3.0.0
*/
function newId() {
// First character is an 'a', it's good practice to tag id to begin with a letter
return 'axxxxxxxxxxx'.replace(/[x]/g, () => {
const val = (Math.random() * 16) | 0;
return val.toString(16);
});
}
const diacritics = {
'\u24B6': 'A',
'\uFF21': 'A',
'\u00C0': 'A',
'\u00C1': 'A',
'\u00C2': 'A',
'\u1EA6': 'A',
'\u1EA4': 'A',
'\u1EAA': 'A',
'\u1EA8': 'A',
'\u00C3': 'A',
'\u0100': 'A',
'\u0102': 'A',
'\u1EB0': 'A',
'\u1EAE': 'A',
'\u1EB4': 'A',
'\u1EB2': 'A',
'\u0226': 'A',
'\u01E0': 'A',
'\u00C4': 'A',
'\u01DE': 'A',
'\u1EA2': 'A',
'\u00C5': 'A',
'\u01FA': 'A',
'\u01CD': 'A',
'\u0200': 'A',
'\u0202': 'A',
'\u1EA0': 'A',
'\u1EAC': 'A',
'\u1EB6': 'A',
'\u1E00': 'A',
'\u0104': 'A',
'\u023A': 'A',
'\u2C6F': 'A',
'\uA732': 'AA',
'\u00C6': 'AE',
'\u01FC': 'AE',
'\u01E2': 'AE',
'\uA734': 'AO',
'\uA736': 'AU',
'\uA738': 'AV',
'\uA73A': 'AV',
'\uA73C': 'AY',
'\u24B7': 'B',
'\uFF22': 'B',
'\u1E02': 'B',
'\u1E04': 'B',
'\u1E06': 'B',
'\u0243': 'B',
'\u0182': 'B',
'\u0181': 'B',
'\u24B8': 'C',
'\uFF23': 'C',
'\u0106': 'C',
'\u0108': 'C',
'\u010A': 'C',
'\u010C': 'C',
'\u00C7': 'C',
'\u1E08': 'C',
'\u0187': 'C',
'\u023B': 'C',
'\uA73E': 'C',
'\u24B9': 'D',
'\uFF24': 'D',
'\u1E0A': 'D',
'\u010E': 'D',
'\u1E0C': 'D',
'\u1E10': 'D',
'\u1E12': 'D',
'\u1E0E': 'D',
'\u0110': 'D',
'\u018B': 'D',
'\u018A': 'D',
'\u0189': 'D',
'\uA779': 'D',
'\u01F1': 'DZ',
'\u01C4': 'DZ',
'\u01F2': 'Dz',
'\u01C5': 'Dz',
'\u24BA': 'E',
'\uFF25': 'E',
'\u00C8': 'E',
'\u00C9': 'E',
'\u00CA': 'E',
'\u1EC0': 'E',
'\u1EBE': 'E',
'\u1EC4': 'E',
'\u1EC2': 'E',
'\u1EBC': 'E',
'\u0112': 'E',
'\u1E14': 'E',
'\u1E16': 'E',
'\u0114': 'E',
'\u0116': 'E',
'\u00CB': 'E',
'\u1EBA': 'E',
'\u011A': 'E',
'\u0204': 'E',
'\u0206': 'E',
'\u1EB8': 'E',
'\u1EC6': 'E',
'\u0228': 'E',
'\u1E1C': 'E',
'\u0118': 'E',
'\u1E18': 'E',
'\u1E1A': 'E',
'\u0190': 'E',
'\u018E': 'E',
'\u24BB': 'F',
'\uFF26': 'F',
'\u1E1E': 'F',
'\u0191': 'F',
'\uA77B': 'F',
'\u24BC': 'G',
'\uFF27': 'G',
'\u01F4': 'G',
'\u011C': 'G',
'\u1E20': 'G',
'\u011E': 'G',
'\u0120': 'G',
'\u01E6': 'G',
'\u0122': 'G',
'\u01E4': 'G',
'\u0193': 'G',
'\uA7A0': 'G',
'\uA77D': 'G',
'\uA77E': 'G',
'\u24BD': 'H',
'\uFF28': 'H',
'\u0124': 'H',
'\u1E22': 'H',
'\u1E26': 'H',
'\u021E': 'H',
'\u1E24': 'H',
'\u1E28': 'H',
'\u1E2A': 'H',
'\u0126': 'H',
'\u2C67': 'H',
'\u2C75': 'H',
'\uA78D': 'H',
'\u24BE': 'I',
'\uFF29': 'I',
'\u00CC': 'I',
'\u00CD': 'I',
'\u00CE': 'I',
'\u0128': 'I',
'\u012A': 'I',
'\u012C': 'I',
'\u0130': 'I',
'\u00CF': 'I',
'\u1E2E': 'I',
'\u1EC8': 'I',
'\u01CF': 'I',
'\u0208': 'I',
'\u020A': 'I',
'\u1ECA': 'I',
'\u012E': 'I',
'\u1E2C': 'I',
'\u0197': 'I',
'\u24BF': 'J',
'\uFF2A': 'J',
'\u0134': 'J',
'\u0248': 'J',
'\u24C0': 'K',
'\uFF2B': 'K',
'\u1E30': 'K',
'\u01E8': 'K',
'\u1E32': 'K',
'\u0136': 'K',
'\u1E34': 'K',
'\u0198': 'K',
'\u2C69': 'K',
'\uA740': 'K',
'\uA742': 'K',
'\uA744': 'K',
'\uA7A2': 'K',
'\u24C1': 'L',
'\uFF2C': 'L',
'\u013F': 'L',
'\u0139': 'L',
'\u013D': 'L',
'\u1E36': 'L',
'\u1E38': 'L',
'\u013B': 'L',
'\u1E3C': 'L',
'\u1E3A': 'L',
'\u0141': 'L',
'\u023D': 'L',
'\u2C62': 'L',
'\u2C60': 'L',
'\uA748': 'L',
'\uA746': 'L',
'\uA780': 'L',
'\u01C7': 'LJ',
'\u01C8': 'Lj',
'\u24C2': 'M',
'\uFF2D': 'M',
'\u1E3E': 'M',
'\u1E40': 'M',
'\u1E42': 'M',
'\u2C6E': 'M',
'\u019C': 'M',
'\u24C3': 'N',
'\uFF2E': 'N',
'\u01F8': 'N',
'\u0143': 'N',
'\u00D1': 'N',
'\u1E44': 'N',
'\u0147': 'N',
'\u1E46': 'N',
'\u0145': 'N',
'\u1E4A': 'N',
'\u1E48': 'N',
'\u0220': 'N',
'\u019D': 'N',
'\uA790': 'N',
'\uA7A4': 'N',
'\u01CA': 'NJ',
'\u01CB': 'Nj',
'\u24C4': 'O',
'\uFF2F': 'O',
'\u00D2': 'O',
'\u00D3': 'O',
'\u00D4': 'O',
'\u1ED2': 'O',
'\u1ED0': 'O',
'\u1ED6': 'O',
'\u1ED4': 'O',
'\u00D5': 'O',
'\u1E4C': 'O',
'\u022C': 'O',
'\u1E4E': 'O',
'\u014C': 'O',
'\u1E50': 'O',
'\u1E52': 'O',
'\u014E': 'O',
'\u022E': 'O',
'\u0230': 'O',
'\u00D6': 'O',
'\u022A': 'O',
'\u1ECE': 'O',
'\u0150': 'O',
'\u01D1': 'O',
'\u020C': 'O',
'\u020E': 'O',
'\u01A0': 'O',
'\u1EDC': 'O',
'\u1EDA': 'O',
'\u1EE0': 'O',
'\u1EDE': 'O',
'\u1EE2': 'O',
'\u1ECC': 'O',
'\u1ED8': 'O',
'\u01EA': 'O',
'\u01EC': 'O',
'\u00D8': 'O',
'\u01FE': 'O',
'\u0186': 'O',
'\u019F': 'O',
'\uA74A': 'O',
'\uA74C': 'O',
'\u01A2': 'OI',
'\uA74E': 'OO',
'\u0222': 'OU',
'\u24C5': 'P',
'\uFF30': 'P',
'\u1E54': 'P',
'\u1E56': 'P',
'\u01A4': 'P',
'\u2C63': 'P',
'\uA750': 'P',
'\uA752': 'P',
'\uA754': 'P',
'\u24C6': 'Q',
'\uFF31': 'Q',
'\uA756': 'Q',
'\uA758': 'Q',
'\u024A': 'Q',
'\u24C7': 'R',
'\uFF32': 'R',
'\u0154': 'R',
'\u1E58': 'R',
'\u0158': 'R',
'\u0210': 'R',
'\u0212': 'R',
'\u1E5A': 'R',
'\u1E5C': 'R',
'\u0156': 'R',
'\u1E5E': 'R',
'\u024C': 'R',
'\u2C64': 'R',
'\uA75A': 'R',
'\uA7A6': 'R',
'\uA782': 'R',
'\u24C8': 'S',
'\uFF33': 'S',
'\u1E9E': 'S',
'\u015A': 'S',
'\u1E64': 'S',
'\u015C': 'S',
'\u1E60': 'S',
'\u0160': 'S',
'\u1E66': 'S',
'\u1E62': 'S',
'\u1E68': 'S',
'\u0218': 'S',
'\u015E': 'S',
'\u2C7E': 'S',
'\uA7A8': 'S',
'\uA784': 'S',
'\u24C9': 'T',
'\uFF34': 'T',
'\u1E6A': 'T',
'\u0164': 'T',
'\u1E6C': 'T',
'\u021A': 'T',
'\u0162': 'T',
'\u1E70': 'T',
'\u1E6E': 'T',
'\u0166': 'T',
'\u01AC': 'T',
'\u01AE': 'T',
'\u023E': 'T',
'\uA786': 'T',
'\uA728': 'TZ',
'\u24CA': 'U',
'\uFF35': 'U',
'\u00D9': 'U',
'\u00DA': 'U',
'\u00DB': 'U',
'\u0168': 'U',
'\u1E78': 'U',
'\u016A': 'U',
'\u1E7A': 'U',
'\u016C': 'U',
'\u00DC': 'U',
'\u01DB': 'U',
'\u01D7': 'U',
'\u01D5': 'U',
'\u01D9': 'U',
'\u1EE6': 'U',
'\u016E': 'U',
'\u0170': 'U',
'\u01D3': 'U',
'\u0214': 'U',
'\u0216': 'U',
'\u01AF': 'U',
'\u1EEA': 'U',
'\u1EE8': 'U',
'\u1EEE': 'U',
'\u1EEC': 'U',
'\u1EF0': 'U',
'\u1EE4': 'U',
'\u1E72': 'U',
'\u0172': 'U',
'\u1E76': 'U',
'\u1E74': 'U',
'\u0244': 'U',
'\u24CB': 'V',
'\uFF36': 'V',
'\u1E7C': 'V',
'\u1E7E': 'V',
'\u01B2': 'V',
'\uA75E': 'V',
'\u0245': 'V',
'\uA760': 'VY',
'\u24CC': 'W',
'\uFF37': 'W',
'\u1E80': 'W',
'\u1E82': 'W',
'\u0174': 'W',
'\u1E86': 'W',
'\u1E84': 'W',
'\u1E88': 'W',
'\u2C72': 'W',
'\u24CD': 'X',
'\uFF38': 'X',
'\u1E8A': 'X',
'\u1E8C': 'X',
'\u24CE': 'Y',
'\uFF39': 'Y',
'\u1EF2': 'Y',
'\u00DD': 'Y',
'\u0176': 'Y',
'\u1EF8': 'Y',
'\u0232': 'Y',
'\u1E8E': 'Y',
'\u0178': 'Y',
'\u1EF6': 'Y',
'\u1EF4': 'Y',
'\u01B3': 'Y',
'\u024E': 'Y',
'\u1EFE': 'Y',
'\u24CF': 'Z',
'\uFF3A': 'Z',
'\u0179': 'Z',
'\u1E90': 'Z',
'\u017B': 'Z',
'\u017D': 'Z',
'\u1E92': 'Z',
'\u1E94': 'Z',
'\u01B5': 'Z',
'\u0224': 'Z',
'\u2C7F': 'Z',
'\u2C6B': 'Z',
'\uA762': 'Z',
'\u24D0': 'a',
'\uFF41': 'a',
'\u1E9A': 'a',
'\u00E0': 'a',
'\u00E1': 'a',
'\u00E2': 'a',
'\u1EA7': 'a',
'\u1EA5': 'a',
'\u1EAB': 'a',
'\u1EA9': 'a',
'\u00E3': 'a',
'\u0101': 'a',
'\u0103': 'a',
'\u1EB1': 'a',
'\u1EAF': 'a',
'\u1EB5': 'a',
'\u1EB3': 'a',
'\u0227': 'a',
'\u01E1': 'a',
'\u00E4': 'a',
'\u01DF': 'a',
'\u1EA3': 'a',
'\u00E5': 'a',
'\u01FB': 'a',
'\u01CE': 'a',
'\u0201': 'a',
'\u0203': 'a',
'\u1EA1': 'a',
'\u1EAD': 'a',
'\u1EB7': 'a',
'\u1E01': 'a',
'\u0105': 'a',
'\u2C65': 'a',
'\u0250': 'a',
'\uA733': 'aa',
'\u00E6': 'ae',
'\u01FD': 'ae',
'\u01E3': 'ae',
'\uA735': 'ao',
'\uA737': 'au',
'\uA739': 'av',
'\uA73B': 'av',
'\uA73D': 'ay',
'\u24D1': 'b',
'\uFF42': 'b',
'\u1E03': 'b',
'\u1E05': 'b',
'\u1E07': 'b',
'\u0180': 'b',
'\u0183': 'b',
'\u0253': 'b',
'\u24D2': 'c',
'\uFF43': 'c',
'\u0107': 'c',
'\u0109': 'c',
'\u010B': 'c',
'\u010D': 'c',
'\u00E7': 'c',
'\u1E09': 'c',
'\u0188': 'c',
'\u023C': 'c',
'\uA73F': 'c',
'\u2184': 'c',
'\u24D3': 'd',
'\uFF44': 'd',
'\u1E0B': 'd',
'\u010F': 'd',
'\u1E0D': 'd',
'\u1E11': 'd',
'\u1E13': 'd',
'\u1E0F': 'd',
'\u0111': 'd',
'\u018C': 'd',
'\u0256': 'd',
'\u0257': 'd',
'\uA77A': 'd',
'\u01F3': 'dz',
'\u01C6': 'dz',
'\u24D4': 'e',
'\uFF45': 'e',
'\u00E8': 'e',
'\u00E9': 'e',
'\u00EA': 'e',
'\u1EC1': 'e',
'\u1EBF': 'e',
'\u1EC5': 'e',
'\u1EC3': 'e',
'\u1EBD': 'e',
'\u0113': 'e',
'\u1E15': 'e',
'\u1E17': 'e',
'\u0115': 'e',
'\u0117': 'e',
'\u00EB': 'e',
'\u1EBB': 'e',
'\u011B': 'e',
'\u0205': 'e',
'\u0207': 'e',
'\u1EB9': 'e',
'\u1EC7': 'e',
'\u0229': 'e',
'\u1E1D': 'e',
'\u0119': 'e',
'\u1E19': 'e',
'\u1E1B': 'e',
'\u0247': 'e',
'\u025B': 'e',
'\u01DD': 'e',
'\u24D5': 'f',
'\uFF46': 'f',
'\u1E1F': 'f',
'\u0192': 'f',
'\uA77C': 'f',
'\u24D6': 'g',
'\uFF47': 'g',
'\u01F5': 'g',
'\u011D': 'g',
'\u1E21': 'g',
'\u011F': 'g',
'\u0121': 'g',
'\u01E7': 'g',
'\u0123': 'g',
'\u01E5': 'g',
'\u0260': 'g',
'\uA7A1': 'g',
'\u1D79': 'g',
'\uA77F': 'g',
'\u24D7': 'h',
'\uFF48': 'h',
'\u0125': 'h',
'\u1E23': 'h',
'\u1E27': 'h',
'\u021F': 'h',
'\u1E25': 'h',
'\u1E29': 'h',
'\u1E2B': 'h',
'\u1E96': 'h',
'\u0127': 'h',
'\u2C68': 'h',
'\u2C76': 'h',
'\u0265': 'h',
'\u0195': 'hv',
'\u24D8': 'i',
'\uFF49': 'i',
'\u00EC': 'i',
'\u00ED': 'i',
'\u00EE': 'i',
'\u0129': 'i',
'\u012B': 'i',
'\u012D': 'i',
'\u00EF': 'i',
'\u1E2F': 'i',
'\u1EC9': 'i',
'\u01D0': 'i',
'\u0209': 'i',
'\u020B': 'i',
'\u1ECB': 'i',
'\u012F': 'i',
'\u1E2D': 'i',
'\u0268': 'i',
'\u0131': 'i',
'\u24D9': 'j',
'\uFF4A': 'j',
'\u0135': 'j',
'\u01F0': 'j',
'\u0249': 'j',
'\u24DA': 'k',
'\uFF4B': 'k',
'\u1E31': 'k',
'\u01E9': 'k',
'\u1E33': 'k',
'\u0137': 'k',
'\u1E35': 'k',
'\u0199': 'k',
'\u2C6A': 'k',
'\uA741': 'k',
'\uA743': 'k',
'\uA745': 'k',
'\uA7A3': 'k',
'\u24DB': 'l',
'\uFF4C': 'l',
'\u0140': 'l',
'\u013A': 'l',
'\u013E': 'l',
'\u1E37': 'l',
'\u1E39': 'l',
'\u013C': 'l',
'\u1E3D': 'l',
'\u1E3B': 'l',
'\u017F': 'l',
'\u0142': 'l',
'\u019A': 'l',
'\u026B': 'l',
'\u2C61': 'l',
'\uA749': 'l',
'\uA781': 'l',
'\uA747': 'l',
'\u01C9': 'lj',
'\u24DC': 'm',
'\uFF4D': 'm',
'\u1E3F': 'm',
'\u1E41': 'm',
'\u1E43': 'm',
'\u0271': 'm',
'\u026F': 'm',
'\u24DD': 'n',
'\uFF4E': 'n',
'\u01F9': 'n',
'\u0144': 'n',
'\u00F1': 'n',
'\u1E45': 'n',
'\u0148': 'n',
'\u1E47': 'n',
'\u0146': 'n',
'\u1E4B': 'n',
'\u1E49': 'n',
'\u019E': 'n',
'\u0272': 'n',
'\u0149': 'n',
'\uA791': 'n',
'\uA7A5': 'n',
'\u01CC': 'nj',
'\u24DE': 'o',
'\uFF4F': 'o',
'\u00F2': 'o',
'\u00F3': 'o',
'\u00F4': 'o',
'\u1ED3': 'o',
'\u1ED1': 'o',
'\u1ED7': 'o',
'\u1ED5': 'o',
'\u00F5': 'o',
'\u1E4D': 'o',
'\u022D': 'o',
'\u1E4F': 'o',
'\u014D': 'o',
'\u1E51': 'o',
'\u1E53': 'o',
'\u014F': 'o',
'\u022F': 'o',
'\u0231': 'o',
'\u00F6': 'o',
'\u022B': 'o',
'\u1ECF': 'o',
'\u0151': 'o',
'\u01D2': 'o',
'\u020D': 'o',
'\u020F': 'o',
'\u01A1': 'o',
'\u1EDD': 'o',
'\u1EDB': 'o',
'\u1EE1': 'o',
'\u1EDF': 'o',
'\u1EE3': 'o',
'\u1ECD': 'o',
'\u1ED9': 'o',
'\u01EB': 'o',
'\u01ED': 'o',
'\u00F8': 'o',
'\u01FF': 'o',
'\u0254': 'o',
'\uA74B': 'o',
'\uA74D': 'o',
'\u0275': 'o',
'\u01A3': 'oi',
'\u0223': 'ou',
'\uA74F': 'oo',
'\u24DF': 'p',
'\uFF50': 'p',
'\u1E55': 'p',
'\u1E57': 'p',
'\u01A5': 'p',
'\u1D7D': 'p',
'\uA751': 'p',
'\uA753': 'p',
'\uA755': 'p',
'\u24E0': 'q',
'\uFF51': 'q',
'\u024B': 'q',
'\uA757': 'q',
'\uA759': 'q',
'\u24E1': 'r',
'\uFF52': 'r',
'\u0155': 'r',
'\u1E59': 'r',
'\u0159': 'r',
'\u0211': 'r',
'\u0213': 'r',
'\u1E5B': 'r',
'\u1E5D': 'r',
'\u0157': 'r',
'\u1E5F': 'r',
'\u024D': 'r',
'\u027D': 'r',
'\uA75B': 'r',
'\uA7A7': 'r',
'\uA783': 'r',
'\u24E2': 's',
'\uFF53': 's',
'\u00DF': 's',
'\u015B': 's',
'\u1E65': 's',
'\u015D': 's',
'\u1E61': 's',
'\u0161': 's',
'\u1E67': 's',
'\u1E63': 's',
'\u1E69': 's',
'\u0219': 's',
'\u015F': 's',
'\u023F': 's',
'\uA7A9': 's',
'\uA785': 's',
'\u1E9B': 's',
'\u24E3': 't',
'\uFF54': 't',
'\u1E6B': 't',
'\u1E97': 't',
'\u0165': 't',
'\u1E6D': 't',
'\u021B': 't',
'\u0163': 't',
'\u1E71': 't',
'\u1E6F': 't',
'\u0167': 't',
'\u01AD': 't',
'\u0288': 't',
'\u2C66': 't',
'\uA787': 't',
'\uA729': 'tz',
'\u24E4': 'u',
'\uFF55': 'u',
'\u00F9': 'u',
'\u00FA': 'u',
'\u00FB': 'u',
'\u0169': 'u',
'\u1E79': 'u',
'\u016B': 'u',
'\u1E7B': 'u',
'\u016D': 'u',
'\u00FC': 'u',
'\u01DC': 'u',
'\u01D8': 'u',
'\u01D6': 'u',
'\u01DA': 'u',
'\u1EE7': 'u',
'\u016F': 'u',
'\u0171': 'u',
'\u01D4': 'u',
'\u0215': 'u',
'\u0217': 'u',
'\u01B0': 'u',
'\u1EEB': 'u',
'\u1EE9': 'u',
'\u1EEF': 'u',
'\u1EED': 'u',
'\u1EF1': 'u',
'\u1EE5': 'u',
'\u1E73': 'u',
'\u0173': 'u',
'\u1E77': 'u',
'\u1E75': 'u',
'\u0289': 'u',
'\u24E5': 'v',
'\uFF56': 'v',
'\u1E7D': 'v',
'\u1E7F': 'v',
'\u028B': 'v',
'\uA75F': 'v',
'\u028C': 'v',
'\uA761': 'vy',
'\u24E6': 'w',
'\uFF57': 'w',
'\u1E81': 'w',
'\u1E83': 'w',
'\u0175': 'w',
'\u1E87': 'w',
'\u1E85': 'w',
'\u1E98': 'w',
'\u1E89': 'w',
'\u2C73': 'w',
'\u24E7': 'x',
'\uFF58': 'x',
'\u1E8B': 'x',
'\u1E8D': 'x',
'\u24E8': 'y',
'\uFF59': 'y',
'\u1EF3': 'y',
'\u00FD': 'y',
'\u0177': 'y',
'\u1EF9': 'y',
'\u0233': 'y',
'\u1E8F': 'y',
'\u00FF': 'y',
'\u1EF7': 'y',
'\u1E99': 'y',
'\u1EF5': 'y',
'\u01B4': 'y',
'\u024F': 'y',
'\u1EFF': 'y',
'\u24E9': 'z',
'\uFF5A': 'z',
'\u017A': 'z',
'\u1E91': 'z',
'\u017C': 'z',
'\u017E': 'z',
'\u1E93': 'z',
'\u1E95': 'z',
'\u01B6': 'z',
'\u0225': 'z',
'\u0240': 'z',
'\u2C6C': 'z',
'\uA763': 'z',
'\u0386': '\u0391',
'\u0388': '\u0395',
'\u0389': '\u0397',
'\u038A': '\u0399',
'\u03AA': '\u0399',
'\u038C': '\u039F',
'\u038E': '\u03A5',
'\u03AB': '\u03A5',
'\u038F': '\u03A9',
'\u03AC': '\u03B1',
'\u03AD': '\u03B5',
'\u03AE': '\u03B7',
'\u03AF': '\u03B9',
'\u03CA': '\u03B9',
'\u0390': '\u03B9',
'\u03CC': '\u03BF',
'\u03CD': '\u03C5',
'\u03CB': '\u03C5',
'\u03B0': '\u03C5',
'\u03C9': '\u03C9',
'\u03C2': '\u03C3',
};
/**
* Normalizes accented and special characters for case-insensitive option searching.
*
* @param text - The text.
* @returns The strip special chars result.
*
* @since 3.0.0
*/
function stripSpecialChars(text) {
const match = (a) => diacritics[a] || a;
// eslint-disable-next-line no-control-regex
return text.replace(/[^\u0000-\u007E]/g, match);
}
/**
* Manages source, filtered, grouped, marked, and selected options for an ng-select instance.
*
* @since 3.0.0
*/
class ItemsList {
/**
* Creates an instance of ItemsList.
*
* @param _ngSelect - The ng select.
* @param _selectionModel - The selection model.
*
* @since 3.0.0
*/
constructor(_ngSelect, _selectionModel) {
this._ngSelect = _ngSelect;
this._selectionModel = _selectionModel;
this._items = [];
this._filteredItems = [];
this._markedIndex = -1;
}
/**
* Gets the items.
*
* @returns The items.
*
* @since 3.0.0
*/
get items() {
return this._items;
}
/**
* Gets the filtered items.
*
* @returns The filtered items.
*
* @since 3.0.0
*/
get filteredItems() {
return this._filteredItems;
}
/**
* Gets the marked index.
*
* @returns The marked index.
*
* @since 3.0.0
*/
get markedIndex() {
return this._markedIndex;
}
/**
* Gets the selected items.
*
* @returns The selected items.
*
* @since 3.0.0
*/
get selectedItems() {
return this._selectionModel.value;
}
/**
* Gets the marked item.
*
* @returns The marked item.
*
* @since 3.0.0
*/
get markedItem() {
return this._filteredItems[this._markedIndex];
}
/**
* Determines whether every available option is already selected.
*
* @returns Whether no items to select.
*
* @since 3.0.0
*/
get noItemsToSelect() {
return this._ngSelect.hideSelected() && this._items.length === this.selectedItems.length;
}
/**
* Determines whether the configured selection limit has been reached.
*
* @returns Whether max items selected.
*
* @since 3.0.0
*/
get maxItemsSelected() {
return this._ngSelect.multiple() && this._ngSelect.maxSelectedItems() <= this.selectedItems.length;
}
/**
* Gets the last selected item.
*
* @returns The last selected item.
*
* @since 3.0.0
*/
get lastSelectedItem() {
let i = this.selectedItems.length - 1;
for (; i >= 0; i--) {
const item = this.selectedItems[i];
if (!item.disabled) {
return item;
}
}
return null;
}
/**
* Maps and groups a new immutable collection of source items.
*
* @param items - The options to process.
*
* @since 3.0.0
*/
setItems(items) {
this._items = items.map((item, index) => this.mapItem(item, index));
const groupBy = this._ngSelect.groupBy();
if (groupBy) {
this._groups = this._groupBy(this._items, groupBy);
this._items = this._flatten(this._groups);
}
else {
this._groups = new Map();
this._groups.set(undefined, this._items);
}
this._filteredItems = [...this._items];
}
/**
* Selects an option according to the active selection mode.
*
* @param item - The option to process.
*
* @since 3.0.0
*/
select(item) {
if (item.selected || this.maxItemsSelected) {
return;
}
const multiple = this._ngSelect.multiple();
if (!multiple) {
this.clearSelected(false);
}
this._selectionModel.select(item, multiple, this._ngSelect.selectableGroupAsModel());
if (this._ngSelect.hideSelected()) {
this._hideSelected(item);
}
}
/**
* Removes an option from the current selection.
*
* @param item - The option to process.
*
* @since 3.0.0
*/
unselect(item) {
if (!item.selected) {
return;
}
const multiple = this._ngSelect.multiple();
this._selectionModel.unselect(item, multiple);
if (this._ngSelect.hideSelected() && isDefined(item.index) && multiple) {
this._showSelected(item);
}
}
/**
* Finds the option matching a model value.
*
* @param value - The value to process.
* @returns The matching option, when one exists.
*
* @since 3.0.0
*/
findItem(value) {
let findBy;
if (this._ngSelect.compareWith()) {
findBy = (item) => this._ngSelect.compareWith()(item.value, value);
}
else if (this._ngSelect.bindValue()) {
findBy = (item) => !item.children && this.resolveNested(item.value, this._ngSelect.bindValue()) === value;
}
else {
findBy = (item) => item.value === value || (!item.children && item.label && item.label === this.resolveNested(value, this._ngSelect.bindLabel()));
}
return this._items.find((item) => findBy(item));
}
/**
* Maps and appends a new option to the list.
*
* @param item - The option to process.
*
* @since 3.0.0
*/
addItem(item) {
const option = this.mapItem(item, this._items.length);
this._items.push(option);
this._filteredItems.push(option);
return option;
}
/**
* Clears selected options while optionally retaining disabled selections.
*
* @param keepDisabled - The keep disabled.
*
* @since 3.0.0
*/
clearSelected(keepDisabled) {
this._selectionModel.clear(keepDisabled);
this._items.forEach((item) => {
item.selected = keepDisabled && item.selected && item.disabled;
item.marked = false;
});
if (this._ngSelect.hideSelected()) {
this.resetFilteredItems();
}
}
/**
* Finds an option using a case-insensitive label comparison.
*
* @param term - The term.
*
* @since 3.0.0
*/
findByLabel(term) {
term = stripSpecialChars(term).toLocaleLowerCase();
return this.filteredItems.find((item) => {
const label = stripSpecialChars(item.label).toLocaleLowerCase();
return label.substr(0, term.length) === term;
});
}
/**
* Filters the available options using the current search configuration.
*
* @param term - The term.
*
* @since 3.0.0
*/
filter(term) {
if (!term) {
this.resetFilteredItems();
return;
}
this._filteredItems = [];
term = this._ngSelect.searchFn() ? term : stripSpecialChars(term).toLocaleLowerCase();
const match = this._ngSelect.searchFn() || this._defaultSearchFn;
const hideSelected = this._ngSelect.hideSelected();
for (const key of Array.from(this._groups.keys())) {
const matchedItems = [];
for (const item of this._groups.get(key)) {
if (hideSelected && ((item.parent && item.parent.selected) || item.selected)) {
continue;
}
const searchItem = this._ngSelect.searchFn() ? item.value : item;
if (match(term, searchItem)) {
matchedItems.push(item);
}
}
if (matchedItems.length > 0) {
const [last] = matchedItems.slice(-1);
if (last.parent) {
const head = this._items.find((x) => x === last.parent);
this._filteredItems.push(head);
}
this._filteredItems.push(...matchedItems);
}
}
}
/**
* Restores the unfiltered option collection.
*
* @since 3.0.0
*/
resetFilteredItems() {
if (this._filteredItems.length === this._items.length) {
return;
}
if (this._ngSelect.hideSelected() && this.selectedItems.length > 0) {
this._filteredItems = this._items.filter((x) => !x.selected);
}
else {
this._filteredItems = this._items;
}
}
/**
* Clears the currently marked option.
*
* @since 3.0.0
*/
unmarkItem() {
this._markedIndex = -1;
}
/**
* Marks the next enabled option.
*
* @since 3.0.0
*/
markNextItem() {
this._stepToItem(+1);
}
/**
* Marks the previous enabled option.
*
* @since 3.0.0
*/
markPreviousItem() {
this._stepToItem(-1);
}
/**
* Marks a specific option for keyboard interaction.
*
* @param item - The option to process.
*
* @since 3.0.0
*/
markItem(item) {
this._markedIndex = this._filteredItems.indexOf(item);
}
/**
* Marks the selected option or the configured default option.
*
* @param markDefault - The mark default.
*
* @since 3.0.0
*/
markSelectedOrDefault(markDefault) {
if (this._filteredItems.length === 0) {
return;
}
const lastMarkedIndex = this._getLastMarkedIndex();
if (lastMarkedIndex > -1) {
this._markedIndex = lastMarkedIndex;
}
else {
this._markedIndex = markDefault ? this.filteredItems.findIndex((x) => !x.disabled) : -1;
}
}
/**
* Resolves a dot-separated property path from an option value.
*
* @param option - The option to process.
* @param key - The key.
* @returns The resolved nested value.
*
* @since 3.0.0
*/
resolveNested(option, key) {
if (!isObject(option)) {
return option;
}
if (!key) {
return undefined;
}
if (key.indexOf('.') === -1) {
return option[key];
}
else {
const keys = key.split('.');
let value = option;
for (let i = 0, len = keys.length; i < len; ++i) {
if (value == null) {
return null;
}
value = value[keys[i]];
}
return value;
}
}
/**
* Maps a source value to the internal option representation.
*
* @param item - The option to process.
* @param index - The index.
* @returns The mapped internal option.
*
* @since 3.0.0
*/
mapItem(item, index) {
const hasNgOptionLabel = isObject(item) && '$ngOptionLabel' in item;
const hasNgOptionValue = isObject(item) && '$ngOptionValue' in item;
const hasNgOptionClasses = isObject(item) && '$ngOptionClasses' in item;
const label = hasNgOptionLabel ? item.$ngOptionLabel : this.resolveNested(item, this._ngSelect.bindLabel());
const value = hasNgOptionValue ? item.$ngOptionValue : item;
return {
index,
label: isDefined(label) ? label.toString() : '',
value,
disabled: item && item.disabled ? item.disabled : false,
classes: hasNgOptionClasses ? item.$ngOptionClasses : '',
htmlId: `${this._ngSelect.dropdownId}-${index}`,
};
}
/**
* Remaps selected values after the source collection changes.
*
* @since 3.0.0
*/
mapSelectedItems() {
const multiple = this._ngSelect.multiple();
for (const selected of this.selectedItems) {
const bindValue = this._ngSelect.bindValue();
const value = bindValue ? this.resolveNested(selected.value, bindValue) : selected.value;
const valueFound = isDefined(value);
let item = valueFound ? this.findItem(value) : null;
if (!item && !valueFound && this._ngSelect.compareWith()) {
item = this._items.find((item) => this._ngSelect.compareWith()(item.value, selected.value));
}
this._selectionModel.unselect(selected, multiple);
if (item?.selected) {
// Already re-selected by an earlier iteration (duplicate value); drop the placeholder only
continue;
}
this._selectionModel.select(item || selected, multiple, this._ngSelect.selectableGroupAsModel());
}
if (this._ngSelect.hideSelected()) {
this._filteredItems = this.filteredItems.filter((x) => this.selectedItems.indexOf(x) === -1);
}
}
/**
* Restores a selected option to the visible filtered collection.
*
* @param item - The option to process.
*
* @since 3.0.0
*/
_showSelected(item) {
this._filteredItems.push(item);
if (item.parent) {
const parent = item.parent;
const parentExists = this._filteredItems.find((x) => x === parent);
if (!parentExists) {
this._filteredItems.push(parent);
}
}
else if (item.children) {
for (const child of item.children) {
child.selected = false;
this._filteredItems.push(child);
}
}
this._filteredItems = [...this._filteredItems.sort((a, b) => a.index - b.index)];
}
/**
* Removes a selected option from the visible filtered collection.
*
* @param item - The option to process.
*
* @since 3.0.0
*/
_hideSelected(item) {
this._filteredItems = this._filteredItems.filter((x) => x !== item);
if (item.parent) {
const children = item.parent.children;
if (children.every((x) => x.selected)) {
this._filteredItems = this._filteredItems.filter((x) => x !== item.parent);
}
}
else if (item.children) {
this._filteredItems = this.filteredItems.filter((x) => x.parent !== item);
}
}
/**
* Matches an option using the default normalized-label search.
*
* @param search - The search.
* @param opt - The opt.
*
* @since 3.0.0
*/
_defaultSearchFn(search, opt) {
const label = stripSpecialChars(opt.label).toLocaleLowerCase();
return label.indexOf(search) > -1;
}
/**
* Calculates the next enabled option index.
*
* @param steps - The steps.
*
* @since 3.0.0
*/
_getNextItemIndex(steps) {
if (steps > 0) {
return this._markedIndex >= this._filteredItems.length - 1 ? 0 : this._markedIndex + 1;
}
return this._markedIndex <= 0 ? this._filteredItems.length - 1 : this._markedIndex - 1;
}
/**
* Moves the marked index by the requested number of enabled options.
*
* @param steps - The steps.
*
* @since 3.0.0
*/
_stepToItem(steps) {
if (this._filteredItems.length === 0 || this._filteredItems.every((x) => x.disabled)) {
return;
}
this._markedIndex = this._getNextItemIndex(steps);
if (this.markedItem.disabled) {
this._stepToItem(steps);
}
}
/**
* Returns the index that should remain marked after filtering changes.
*
* @since 3.0.0
*/
_getLastMarkedIndex() {
if (this._ngSelect.hideSelected()) {
return -1;
}
if (this._markedIndex > -1 && this.markedItem === undefined) {
return -1;
}
const selectedIndex = this._getFirstSelectedIndex();
if (this.lastSelectedItem && selectedIndex < 0) {
return -1;
}
return selectedIndex > -1 ? selectedIndex : this.markedIndex;
}
/**
* Index of the first selected, non-disabled option in filtered list order.
* Per the WAI-ARIA listbox pattern, focus lands on the first selected option when the list opens.
*
* @since 23.3.0
*/
_getFirstSelectedIndex() {
let index = -1;
for (const selected of this.selectedItems) {
if (selected.disabled) {
continue;
}
const i = this._filteredItems.indexOf(selected);
if (i > -1 && (index === -1 || i < index)) {
index = i;
}
}
return index;
}
/**
* Groups mapped options using a property name or grouping function.
*
* @param items - The options to process.
* @param prop - The prop.
* @returns The group by result.
*
* @since 3.0.0
*/
_groupBy(items, prop) {
const groups = new Map();
if (items.length === 0) {
return groups;
}
// Check if items are already grouped by given key.
const firstValue = items[0].value;
if (firstValue != null && Array.isArray(firstValue[prop])) {
for (const item of items) {
const children = (item.value?.[prop] || []).map((x, index) => this.mapItem(x, index));
groups.set(item, children);
}
return groups;
}
const isFnKey = isFunction(this._ngSelect.groupBy());
const keyFn = (item) => {
const key = isFnKey ? prop(item.value) : item.value?.[prop];
return isDefined(key) ? key : undefined;
};
// Group items by key.
for (const item of items) {
const key = keyFn(item);
const group = groups.get(key);
if (group) {
group.push(item);
}
else {
groups.set(key, [item]);
}
}
return groups;
}
/**
* Flattens option groups into the render order used by the dropdown.
*
* @param groups - The groups.
*
* @since 3.0.0
*/
_flatten(groups) {
const isGroupByFn = isFunction(this._ngSelect.groupBy());
const items = [];
for (const key of Array.from(groups.keys())) {
let i = items.length;
if (key === undefined) {
const withoutGroup = groups.get(undefined) || [];
items.push(...withoutGroup.map((x) => {
x.index = i++;
x.htmlId = `${this._ngSelect.dropdownId}-${x.index}`;
return x;
}));
continue;
}
const isObjectKey = isObject(key);
const parent = {
label: isObjectKey ? '' : String(key),
children: undefined,
parent: null,
index: i++,
disabled: !this._ngSelect.selectableGroup(),
htmlId: newId(),
};
const groupKey = isGroupByFn ? this._ngSelect.bindLabel() : this._ngSelect.groupBy();
const groupValue = this._ngSelect.groupValue() ||
(() => {
if (isObjectKey) {
return key.value;
}
return { [groupKey]: key };
});
const children = groups.get(key).map((x) => {
x.parent = parent;
x.children = undefined;
x.index = i++;
x.htmlId = `${this._ngSelect.dropdownId}-${x.index}`;
return x;
});
parent.children = children;
parent.value = groupValue(key, children.map((x) => x.value));
items.push(parent);
items.push(...children);
}
return items;
}
}
/**
* Creates the default selection model used by ng-select.
*
* @since 3.0.0
*/
function DefaultSelectionModelFactory() {
return new DefaultSelectionModel();
}
/**
* Implements the default single, multiple, and grouped option selection behavior.
*
* @since 3.0.0
*/
class DefaultSelectionModel {
constructor() {
this._selected = [];
}
/**
* Gets the currently selected options.
*
* @returns The value.
*
* @since 3.0.0
*/
get value() {
return this._selected;
}
/**
* Selects an option according to the active selection mode.
*
* @param item - The option to process.
* @param multiple - Whether multiple selection is enabled.
* @param groupAsModel - Whether a selectable group is represented by its group value.
*
* @since 3.0.0
*/
select(item, multiple, groupAsModel) {
item.selected = true;
if (!item.children || (!multiple && groupAsModel)) {
this._selected.push(item);
}
if (multiple) {
if (item.parent) {
const childrenCount = item.parent.children.length;
const selectedCount = item.parent.children.filter((x) => x.selected).length;
item.parent.selected = childrenCount === selectedCount;
}
else if (item.children) {
this._setChildrenSelectedState(item.children, true);
this._removeChildren(item);
if (groupAsModel && this._activeChildren(item)) {
this._selected = [...this._selected.filter((x) => x.parent !== item), item];
}
else {
this._selected = [...this._selected, ...item.children.filter((x) => !x.disabled)];
}
}
}
}
/**
* Removes an option from the current selection.
*
* @param item - The option to process.
* @param multiple - Whether multiple selection is enabled.
*
* @since 3.0.0
*/
unselect(item, multiple) {
this._selected = this._selected.filter((x) => x !== item);
item.selected = false;
if (multiple) {
if (item.parent && item.parent.selected) {
const children = item.parent.children;
this._removeParent(item.parent);
this._removeChildren(item.parent);
this._selected.push(...children.filter((x) => x !== item && !x.disabled));
item.parent.selected = false;
}
else if (item.children) {
this._setChildrenSelectedState(item.children, false);
this._removeChildren(item);
}
}
}
/**
* Clears the current selection.
*
* @param keepDisabled - Whether disabled options should remain selected.
*
* @since 3.0.0
*/
clear(keepDisabled) {
this._selected = keepDisabled ? this._selected.filter((x) => x.disabled) : [];
}
/**
* Applies a selected state to every enabled child option.
*
* @param children - The children.
* @param selected - Whether the children should be selected.
*
* @since 3.0.0
*/
_setChildrenSelectedState(children, selected) {
for (const child of children) {
if (child.disabled) {
continue;
}
child.selected = selected;
}
}
/**
* Removes a group’s child options from the selected collection.
*
* @param parent - The parent.
*
* @since 3.0.0
*/
_removeChildren(parent) {
this._selected = [...this._selected.filter((x) => x.parent !== parent), ...parent.children.filter((x) => x.parent === parent && x.disabled && x.selected)];
}
/**
* Removes a group option from the selected collection.
*
* @param parent - The parent.
*
* @since 3.0.0
*/
_removeParent(parent) {
this._selected = this._selected.filter((x) => x !== parent);
}
/**
* Determines whether a group contains active child options.
*
* @param item - The option to process.
* @returns Whether active children.
*
* @since 3.7.0
*/
_activeChildren(item) {
return item.children.every((x) => !x.disabled || x.selected);
}
}
/**
* Configuration provider for the NgSelect component. You can inject this service and provide application wide configuration.
*
* @since 3.0.0
*/
class NgSelectConfig {
constructor() {
/** Set placeholder visible even when an item is selected. */
this.fixedPlaceholder = true;
/** Set custom text when filter returns empty result. */
this.notFoundText = 'No items found';
/** Set custom text when using Typeahead. */
this.typeToSearchText = 'Type to search';
/** Set custom text when using tagging. */
this.addTagText = 'Add item';
/** Set custom text when loading items. */
this.loadingText = 'Loading...';
/** Set custom text for clear all icon title. */
this.clearAllText = 'Clear all';
/** Set custom text prefixed to the option label in the aria-label of the remove icon on selected values (multiple mode). */
this.removeText = 'Remove';
/** Set custom aria-label for the dropdown options list. */
this.ariaLabelDropdown = 'Options List';
/** Disable virtual scroll for the dropdown panel. */
this.disableVirtualScroll = true;
/** Open dropdown using enter. */
this.openOnEnter = true;
/** Allows to select dropdown appearance. Set to `outline` or `fill` for Material form-field styles (applies only to Material theme). */
this.appearance = 'underline';
/** Control tab navigation behavior for the clear button. */
this.tabFocusOnClear = true;
/**
* Controls which DOM event is used to detect outside clicks for closing the dropdown.
* Defaults to 'click'. Set to 'mousedown' to handle early outside interactions
* (useful when backdrops load on click and would otherwise close the dropdown).
*/
this.outsideClickEvent = 'click';
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: NgSelectConfig, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: NgSelectConfig, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: NgSelectConfig, decorators: [{
type: Injectable,
args: [{ providedIn: 'root' }]
}] });
/**
* Reports developer-facing warnings through the configured console.
*
* @since 3.0.0
*/
class ConsoleService {
/**
* Writes a developer-facing warning to the console.
*
* @param message - The message.
*
* @since 3.0.0
*/
warn(message) {
console.warn(message);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: ConsoleService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: ConsoleService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: ConsoleService, decorators: [{
type: Injectable,
args: [{ providedIn: 'root' }]
}] });
var KeyCode;
(function (KeyCode) {
KeyCode["Tab"] = "Tab";
KeyCode["Enter"] = "Enter";
KeyCode["Esc"] = "Escape";
KeyCode["Space"] = " ";
KeyCode["ArrowUp"] = "ArrowUp";
KeyCode["ArrowDown"] = "ArrowDown";
KeyCode["Backspace"] = "Backspace";
})(KeyCode || (KeyCode = {}));
/**
* Validates a model value against the select's multiple-selection and value-binding configuration.
*
* @param value - The model value supplied to the select.
* @param options - The binding options used to validate the value.
* @returns The validation result and an optional warning for invalid bindings.
*
* @since 24.0.5
*/
function validateWriteValue(value, options) {
if (!isDefined(value) || (options.multiple && value === '') || (Array.isArray(value) && value.length === 0)) {
return { valid: false };
}
if (options.multiple && !Array.isArray(value)) {
return { valid: false, warning: 'Multiple select form value should be an array.' };
}
const values = options.multiple ? value : [value];
const invalidBinding = values.find((item) => !isDefined(options.compareWith) && isObject(item) && options.bindValue);
if (invalidBinding) {
return {
valid: false,
warning: `Setting object(${JSON.stringify(invalidBinding)}) as your model with bindValue is not allowed unless [compareWith] is used.`,
};
}
return { valid: true };
}
/**
* Creates an option value for a model value that does not match an existing item.
*
* @param value - The unmatched model value.
* @param bindLabel - The property used as the option label.
* @param bindValue - The property used as the bound model value.
* @returns The value in the shape expected by the items list.
*
* @since 24.0.5
*/
function createUnmatchedOptionValue(value, bindLabel, bindValue) {
if (isObject(value) || !bindValue) {
return value;
}
return {
[bindLabel]: null,
[bindValue]: value,
};
}
/**
* Converts selected options into the values emitted through the component model.
*
* @param selectedItems - The currently selected options.
* @param options - The bindings that determine how option values are mapped.
* @param resolveNested - Resolves a nested property from an option value.
* @returns The model values corresponding to the selected options.
*
* @since 24.0.5
*/
function selectedItemsToModel(selectedItems, options, resolveNested) {
return selectedItems.map((item) => {
if (!options.bindValue) {
return item.value;
}
if (item.children) {
const groupKey = options.hasGroupValue ? options.bindValue : options.groupBy;
return item.value[groupKey || options.groupBy];
}
return resolveNested(item.value, options.bindValue);
});
}
/** DI token for SelectionModel implementation. You can provide custom implementation changing selection behaviour. */
const SELECTION_MODEL_FACTORY = new InjectionToken('ng-select-selection-model');
/**
* Transforms an optional attribute value while preserving `undefined`.
*
* @param value - The value to process.
* @returns The optional boolean attribute result.
*
* @since 22.0.0
*/
function optionalBooleanAttribute(value) {
return value == null ? undefined : booleanAttribute(value);
}
/**
* Provides select, multiselect, autocomplete, keyboard, accessibility, and Signal, Reactive, and Template-driven Forms behavior.
*
* @since 3.0.0
*/
class NgSelectComponent {
/**
* Creates an instance of NgSelectComponent.
*
* @since 3.0.0
*/
constructor() {
this.classes = inject(new HostAttributeToken('class'), { optional: true });
this.config = inject(NgSelectConfig);
// signals
this._disabled = signal(false, /* @ts-ignore */
...(ngDevMode ? [{ debugName: "_disabled" }] : /* istanbul ignore next */ []));
// inputs: underscored input() + alias + linkedSignal() for stable public names (back compat)
this._ariaLabelDropdown = input(undefined, { ...(ngDevMode ? { debugName: "_ariaLabelDropdown" } : /* istanbul ignore next */ {}), alias: 'ariaLabelDropdown' });
this.ariaLabelDropdown = linkedSignal(() => this._ariaLabelDropdown(), /* @ts-ignore */
...(ngDevMode ? [{ debugName: "ariaLabelDropdown" }] : /* istanbul ignore next */ []));
this._ariaLabel = input(undefined, { ...(ngDevMode ? { debugName: "_ariaLabel" } : /* istanbul ignore next */ {}), alias: 'ariaLabel' });
this.ariaLabel = linkedSignal(() => this._ariaLabel(), /* @ts-ignore */
...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
/** Marks first item as focused when opening/filtering. */
this._markFirst = input(true, { ...(ngDevMode ? { debugName: "_markFirst" } : /* istanbul ignore next */ {}), alias: 'markFirst', transform: booleanAttribute });
this.markFirst = linkedSignal(() => this._markFirst(), /* @ts-ignore */
...(ngDevMode ? [{ debugName: "markFirst" }] : /* istanbul ignore next */ []));
/** Placeholder text. */
this._placeholder = input(this.config.placeholder, { ...(ngDevMode ? { debugName: "_placeholder" } : /* istanbul ignore next */ {}), alias: 'placeholder' });
this.placeholder = linkedSignal(() => this._placeholder(), /* @ts-ignore */
...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
/** Set placeholder visible even when an item is selected */
this._fixedPlaceholder = input(this.config.fixedPlaceholder, { ...(ngDevMode ? { debugName: "_fixedPlaceholder" } : /* istanbul ignore next */ {}), alias: 'fixedPlaceholder' });
this.fixedPlaceholder = linkedSignal(() => this._fixedPlaceholder(), /* @ts-ignore */
...(ngDevMode ? [{ debugName: "fixedPlaceholder" }] : /* istanbul ignore next */ []));
/** Set custom text when filter returns empty result */
this._notFoundText = input(undefined, { ...(ngDevMode ? { debugName: "_notFoundText" } : /* istanbul ignore next */ {}), alias: 'notFoundText' });
this.notFoundText = linkedSignal(() => this._notFoundText(), /* @ts-ignore */
...(ngDevMode ? [{ debugName: "notFoundText" }] : /* istanbul ignore next */ []));
/** Set custom text when using Typeahead */
this._typeToSearchText = input(undefined, { ...(ngDevMode ? { debugName: "_typeToSearchText" } : /* istanbul ignore next */ {}), alias: 'typeToSearchText' });
this.typeToSearchText = linkedSignal(() => this._typeToSearchText(), /* @ts-ignore */
...(ngDevMode ? [{ debugName: "typeToSearchText" }] : /* istanbul ignore next */ []));
/** Prevent opening of ng-select on right mouse click */
this._preventToggleOnRightClick = input(false, { ...(ngDevMode ? { debugName: "_preventToggleOnRightClick" } : /* istanbul ignore next */ {}), alias: 'preventToggleOnRightClick' });
this.preventToggleOnRightClick = linkedSignal(() => this._preventToggleOnRightClick(), /* @ts-ignore */
...(ngDevMode ? [{ debugName: "preventToggleOnRightClick" }] : /* istanbul ignore next */ []));
/** Set custom text when using tagging */
this._addTagText = input(undefined, { ...(ngDevMode ? { debugName: "_addTagText" } : /* istanbul ignore next */ {}), alias: 'addTagText' });
this.addTagText = linkedSignal(() => this._addTagText(), /* @ts-ignore */
...(ngDevMode ? [{ debugName: "addTagText" }] : /* istanbul ignore next */ []));
/** Set custom text when for loading items */
this._loadingText = input(undefined, { ...(ngDevMode ? { debugName: "_loadingText" } : /* istanbul ignore next */ {}), alias: 'loadingText' });
this.loadingText = linkedSignal(() => this._loadingText(), /* @ts-ignore */
...(ngDevMode ? [{ debugName: "loadingText" }] : /* istanbul ignore next */ []));
/** Set custom text for clear all icon title */
this._clearAllText = input(undefined, { ...(ngDevMode ? { debugName: "_clearAllText" } : /* istanbul ignore next */ {}), alias: 'clearAllText' });
this.clearAllText = linkedSignal(() => this._clearAllText(), /* @ts-ignore */
...(ngDevMode ? [{ debugName: "clearAllText" }] : /* istanbul ignore next */ []));
/** Set custom text prefixed to the option label in the aria-label of the remove icon on selected values (multiple mode) */
this._removeText = input(undefined, { ...(ngDevMode ? { debugName: "_removeText" } : /* istanbul ignore next */ {}), alias: 'removeText' });
this.removeText = linkedSignal(() => this._removeText(), /* @ts-ignore */
...(ngDevMode ? [{ debugName: "removeText" }] : /* istanbul ignore next */ []));
/** Set the dropdown position on open */
this._dropdownPosition = input('auto', { ...(ngDevMode ? { debugName: "_dropdownPosition" } : /* istanbul ignore next */ {}), alias: 'dropdownPosition' });
this.dropdownPosition = linkedSignal(() => this._dropdownPosition(), /* @ts-ignore */
...(ngDevMode ? [{ debugName: "dropdownPosition" }] : /* istanbul ignore next */ []));
/**
* Append the dropdown overlay to any element using a css selector, resolved against the
* select's own root (document or shadow root). The panel keeps its viewport-based
* positioning and top-layer painting; the target controls where the overlay lives in the
* DOM — ancestor-scoped styles, stacking context and focus containment. Defaults to the
* document body.
*/
this._appendTo = input(undefined, { ...(ngDevMode ? { debugName: "_appendTo" } : /* istanbul ignore next */ {}), alias: 'appendTo' });
this.appendTo = linkedSignal(() => this._appendTo(), /* @ts-ignore */
...(ngDevMode ? [{ debugName: "appendTo" }] : /* istanbul ignore next */ []));
/** Configure which DOM event type is used for outside click detection. Use `'mousedown'` to fix issues with backdrop/loading overlays that appear on dropdown open */
this._outsideClickEvent = input(this.config.outsideClickEvent ?? 'click', { ...(ngDevMode ? { debugName: "_outsideClickEvent" } : /* istanbul ignore next */ {}), alias: 'outsideClickEvent' });
this.outsideClickEvent = linkedSignal(() => this._outsideClickEvent(), /* @ts-ignore */
...(ngDevMode ? [{ debugName: "outsideClickEvent" }] : /* istanbul ignore next */ []));
/** You can set the loading state from the outside (e.g. async items loading) */
this._loading = input(false, { ...(ngDevMode ? { debugName: "_loading" } : /* istanbul ignore next */ {}), alias: 'loading', transform: booleanAttribute });
this.loading = linkedSignal(() => this._loading(), /* @ts-ignore */
...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
/** Whether to close the menu when a value is selected */
this._closeOnSelect = input(true, { ...(ngDevMode ? { debugName: "_closeOnSelect" } : /* istanbul ignore next */ {}), alias: 'closeOnSelect', transform: booleanAttribute });
this.closeOnSelect = linkedSignal(() => this._closeOnSelect(), /* @ts-ignore */
...(ngDevMode ? [{ debugName: "closeOnSelect" }] : /* istanbul ignore next */ []));
/** Allows to hide selected items. */
this._hideSelected = input(false, { ...(ngDevMode ? { debugName: "_hideSelected" } : /* istanbul ignore next */ {}), alias: 'hideSelected', transform: booleanAttribute });
this.hideSelected = linkedSignal(() => this._hideSelected(), /* @ts-ignore */
...(ngDevMode ? [{ debugName: "hideSelected" }] : /* istanbul ignore next */ []));
/** Select marked dropdown item using tab. Default `false` */
this._selectOnTab = input(false, { ...(ngDevMode ? { debugName: "_selectOnTab" } : /* istanbul ignore next */ {}), alias: 'selectOnTab', transform: booleanAttribute });
this.selectOnTab = linkedSignal(() => this._selectOnTab(), /* @ts-ignore */
...(ngDevMode ? [{ debugName: "selectOnTab" }] : /* istanbul ignore next */ []));
/** Open dropdown using enter. Default `true` */
this._openOnEnter = input(undefined, { ...(ngDevMode ? { debugName: "_openOnEnter" } : /* istanbul ignore next */ {}), alias: 'openOnEnter', transform: booleanAttribute });
this.openOnEnter = linkedSignal(() => this._openOnEnter(), /* @ts-ignore */
...(ngDevMode ? [{ debugName: "openOnEnter" }] : /* istanbul ignore next */ []));
/** When multiple = true, allows to set a limit number of selection. */
this._maxSelectedItems = input(undefined, { ...(ngDevMode ? { debugName: "_maxSelectedItems" } : /* istanbul ignore next */ {}), alias: 'maxSelectedItems', transform: numberAttribute });
this.maxSelectedItems = linkedSignal(() => this._maxSelectedItems(), /* @ts-ignore */
...(ngDevMode ? [{ debugName: "maxSelectedItems" }] : /* istanbul ignore next */ []));
/** Allow to group items by key or function expression */
this._groupBy = input(undefined, { ...(ngDevMode ? { debugName: "_groupBy" } : /* istanbul ignore next */ {}), alias: 'groupBy' });
this.groupBy = linkedSignal(() => this._groupBy(), /* @ts-ignore */
...(ngDevMode ? [{ debugName: "groupBy" }] : /* istanbul ignore next */ []));
/** Function expression to provide group value */
this._groupValue = input(undefined, { ...(ngDevMode ? { debugName: "_groupValue" } : /* istanbul ignore next */ {}), alias: 'groupValue' });
this.groupValue = linkedSignal(() => this._groupValue(), /* @ts-ignore */
...(ngDevMode ? [{ debugName: "groupValue" }] : /* istanbul ignore next */ []));
/** Used in virtual scrolling, the `bufferAmount` property controls the number of items preloaded in the background to ensure smoother and more seamless scrolling. */
this._bufferAmount = input(4, { ...(ngDevMode ? { debugName: "_bufferAmount" } : /* istanbul ignore next */ {}), alias: 'bufferAmount', transform: numberAttribute });
this.bufferAmount = linkedSignal(() => this._bufferAmount(), /* @ts-ignore */
...(ngDevMode ? [{ debugName: "bufferAmount" }] : /* istanbul ignore next */ []));
/** Enable virtual scroll for better performance when rendering a lot of data */
this._virtualScroll = input(undefined, { ...(ngDevMode ? { debugName: "_virtualScroll" } : /* istanbul ignore next */ {}), alias: 'virtualScroll',
transform: optionalBooleanAttribute });
this.virtualScroll = linkedSignal(() => this._virtualScroll(), /* @ts-ignore */
...(ngDevMode ? [{ debugName: "virtualScroll" }] : /* istanbul ignore next */ []));
this.dropdownVirtualScroll = computed(() => {
const value = this._virtualScroll();
return isDefined(value) ? value : this.isVirtualScrollDisabled(this.config);
}, /* @ts-ignore */
...(ngDevMode ? [{ debugName: "dropdownVirtualScroll" }] : /* istanbul ignore next */ []));
/** Allow to select group when groupBy is used */
this._selectableGroup = input(false, { ...(ngDevMode ? { debugName: "_selectableGroup" } : /* istanbul ignore next */ {}), alias: 'selectableGroup', transform: booleanAttribute });
this.selectableGroup = linkedSignal(() => this._selectableGroup(), /* @ts-ignore */
...(ngDevMode ? [{ debugName: "selectableGroup" }] : /* istanbul ignore next */ []));
/** Control tab navigation behavior for the clear button. Default `true` */
this._tabFocusOnClearButton = input(undefined, { ...(ngDevMode ? { debugName: "_tabFocusOnClearButton" } : /* istanbul ignore next */ {}), alias: 'tabFocusOnClearButton' });
this.tabFocusOnClearButton = linkedSignal(() => this._tabFocusOnClearButton(), /* @ts-ignore */
...(ngDevMode ? [{ debugName: "tabFocusOnClearButton" }] : /* istanbul ignore next */ []));
/** Indicates whether to select all children or group itself */
this._selectableGroupAsModel = input(true, { ...(ngDevMode ? { debugName: "_selectableGroupAsModel" } : /* istanbul ignore next */ {}), alias: 'selectableGroupAsModel', transform: booleanAttribute });
this.selectableGroupAsModel = linkedSignal(() => this._selectableGroupAsModel(), /* @ts-ignore */
...(ngDevMode ? [{ debugName: "selectableGroupAsModel" }] : /* istanbul ignore next */ []));
/** Allow to filter by custom search function */
this._searchFn = input(null, { ...(ngDevMode ? { debugName: "_searchFn" } : /* istanbul ignore next */ {}), alias: 'searchFn' });
this.searchFn = linkedSignal(() => this._searchFn(), /* @ts-ignore */
...(ngDevMode ? [{ debugName: "searchFn" }] : /* istanbul ignore next */ []));
/** Provide custom trackBy function */
this._trackByFn = input(null, { ...(ngDevMode ? { debugName: "_trackByFn" } : /* istanbul ignore next */ {}), alias: 'trackByFn' });
this.trackByFn = linkedSignal(() => this._trackByFn(), /* @ts-ignore */
...(ngDevMode ? [{ debugName: "trackByFn" }] : /* istanbul ignore next */ []));
/** Clear selected values one by one when clicking backspace. Default `true` */
this._clearOnBackspace = input(true, { ...(ngDevMode ? { debugName: "_clearOnBackspace" } : /* istanbul ignore next */ {}), alias: 'clearOnBackspace', transform: booleanAttribute });
this.clearOnBackspace = linkedSignal(() => this._clearOnBackspace(), /* @ts-ignore */
...(ngDevMode ? [{ debugName: "clearOnBackspace" }] : /* istanbul ignore next */ []));
/** Id to associate control with label. */
this._labelForId = input(null, { ...(ngDevMode ? { debugName: "_labelForId" } : /* istanbul ignore next */ {}), alias: 'labelForId' });
this.labelForId = linkedSignal(() => this._labelForId(), /* @ts-ignore */
...(ngDevMode ? [{ debugName: "labelForId" }] : /* istanbul ignore next */ []));
/** Pass custom attributes to underlying `input` element */
this._inputAttrs = input({}, { ...(ngDevMode ? { debugName: "_inputAttrs" } : /* istanbul ignore next */ {}), alias: 'inputAttrs' });
this.inputAttrs = linkedSignal(() => this._inputAttrs(), /* @ts-ignore */
...(ngDevMode ? [{ debugName: "inputAttrs" }] : /* istanbul ignore next */ []));
/** Set tabindex on ng-select */
this._tabIndex = input(undefined, { ...(ngDevMode ? { debugName: "_tabIndex" } : /* istanbul ignore next */ {}), alias: 'tabIndex', transform: numberAttribute });
this.tabIndex = linkedSignal(() => this._tabIndex(), /* @ts-ignore */
...(ngDevMode ? [{ debugName: "tabIndex" }] : /* istanbul ignore next */ []));
/** Prevent user changes while preserving the current selection. */
this._readonly = input(false, { ...(ngDevMode ? { debugName: "_readonly" } : /* istanbul ignore next */ {}), alias: 'readonly', transform: booleanAttribute });
this.readonly = linkedSignal(() => this._readonly(), /* @ts-ignore */
...(ngDevMode ? [{ debugName: "readonly" }] : /* istanbul ignore next */ []));
/** Whether items should be filtered while composition started */
this._searchWhileComposing = input(true, { ...(ngDevMode ? { debugName: "_searchWhileComposing" } : /* istanbul ignore next */ {}), alias: 'searchWhileComposing', transform: booleanAttribute });
this.searchWhileComposing = linkedSignal(() => this._searchWhileComposing(), /* @ts-ignore */
...(ngDevMode ? [{ debugName: "searchWhileComposing" }] : /* istanbul ignore next */ []));
/** Minimum term length to start a search. Should be used with `typeahead` */
this._minTermLength = input(0, { ...(ngDevMode ? { debugName: "_minTermLength" } : /* istanbul ignore next */ {}), alias: 'minTermLength', transform: numberAttribute });
this.minTermLength = linkedSignal(() => this._minTermLength(), /* @ts-ignore */
...(ngDevMode ? [{ debugName: "minTermLength" }] : /* istanbul ignore next */ []));
/** Allow to edit search query if option selected. Default `false`. Works only if multiple is `false`. */
this._editableSearchTerm = input(false, { ...(ngDevMode ? { debugName: "_editableSearchTerm" } : /* istanbul ignore next */ {}), alias: 'editableSearchTerm', transform: booleanAttribute });
this.editableSearchTerm = linkedSignal(() => this._editableSearchTerm(), /* @ts-ignore */
...(ngDevMode ? [{ debugName: "editableSearchTerm" }] : /* istanbul ignore next */ []));
this._ngClass = input(null, { ...(ngDevMode ? { debugName: "_ngClass" } : /* istanbul ignore next */ {}), alias: 'ngClass' });
this.ngClass = linkedSignal(() => this._ngClass(), /* @ts-ignore */
...(ngDevMode ? [{ debugName: "ngClass" }] : /* istanbul ignore next */ []));
/** Additional CSS classes applied to the dropdown panel. Merged with mirrored host `class`, `[class]`, and `[ngClass]` values. */
this._panelClass = input(null, { ...(ngDevMode ? { debugName: "_panelClass" } : /* istanbul ignore next */ {}), alias: 'panelClass' });
this.panelClass = linkedSignal(() => this._panelClass(), /* @ts-ignore */
...(ngDevMode ? [{ debugName: "panelClass" }] : /* istanbul ignore next */ []));
/** Custom autocomplete or advanced filter. */
this._typeahead = input(undefined, { ...(ngDevMode ? { debugName: "_typeahead" } : /* istanbul ignore next */ {}), alias: 'typeahead' });
this.typeahead = linkedSignal(() => this._typeahead(), /* @ts-ignore */
...(ngDevMode ? [{ debugName: "typeahead" }] : /* istanbul ignore next */ []));
/** Allows to select multiple items. */
this._multiple = input(false, { ...(ngDevMode ? { debugName: "_multiple" } : /* istanbul ignore next */ {}), alias: 'multiple', transform: booleanAttribute });
this.multiple = linkedSignal(() => this._multiple(), /* @ts-ignore */
...(ngDevMode ? [{ debugName: "multiple" }] : /* istanbul ignore next */ []));
/** Allows to create custom options. */
this._addTag = input(false, { ...(ngDevMode ? { debugName: "_addTag" } : /* istanbul ignore next */ {}), alias: 'addTag' });
this.addTag = linkedSignal(() => this._addTag(), /* @ts-ignore */
...(ngDevMode ? [{ debugName: "addTag" }] : /* istanbul ignore next */ []));
/** Allow to search for value. Default `true` */
this._searchable = input(true, { ...(ngDevMode ? { debugName: "_searchable" } : /* istanbul ignore next */ {}), alias: 'searchable', transform: booleanAttribute });
this.searchable = linkedSignal(() => this._searchable(), /* @ts-ignore */
...(ngDevMode ? [{ debugName: "searchable" }] : /* istanbul ignore next */ []));
/** Allow to clear selected value. Default `true` */
this._clearable = input(true, { ...(ngDevMode ? { debugName: "_clearable" } : /* istanbul ignore next */ {}), alias: 'clearable', transform: booleanAttribute });
this.clearable = linkedSignal(() => this._clearable(), /* @ts-ignore */
...(ngDevMode ? [{ debugName: "clearable" }] : /* istanbul ignore next */ []));
this._clearKeepsDisabledOptions = input(true, { ...(ngDevMode ? { debugName: "_clearKeepsDisabledOptions" } : /* istanbul ignore next */ {}), alias: 'clearKeepsDisabledOptions', transform: booleanAttribute });
this.clearKeepsDisabledOptions = linkedSignal(() => this._clearKeepsDisabledOptions(), /* @ts-ignore */
...(ngDevMode ? [{ debugName: "clearKeepsDisabledOptions" }] : /* istanbul ignore next */ []));
/** Deselects a selected item when it is clicked in the dropdown. Default `false`. Default `true` when **multiple** is `true` */
this._deselectOnClick = input(undefined, { ...(ngDevMode ? { debugName: "_deselectOnClick" } : /* istanbul ignore next */ {}), alias: 'deselectOnClick' });
this.deselectOnClick = linkedSignal(() => this._deselectOnClick(), /* @ts-ignore */
...(ngDevMode ? [{ debugName: "deselectOnClick" }] : /* istanbul ignore next */ []));
/** Clears search input when item is selected. Default `true`. Default `false` when **closeOnSelect** is `false` */
this._clearSearchOnAdd = input(undefined, { ...(ngDevMode ? { debugName: "_clearSearchOnAdd" } : /* istanbul ignore next */ {}), alias: 'clearSearchOnAdd' });
this.clearSearchOnAdd = linkedSignal(() => this._clearSearchOnAdd(), /* @ts-ignore */
...(ngDevMode ? [{ debugName: "clearSearchOnAdd" }] : /* istanbul ignore next */ []));
/** A function to compare the option values with the selected values. The first argument is a value from an option. The second is a value from the selection(model). A boolean should be returned. */
this._compareWith = input(undefined, { ...(ngDevMode ? { debugName: "_compareWith" } : /* istanbul ignore next */ {}), alias: 'compareWith',
transform: (fn) => {
if (fn !== undefined && fn !== null && !isFunction(fn)) {
throw Error('`compareWith` must be a function.');
}
return fn;
} });
this.compareWith = linkedSignal(() => this._compareWith(), /* @ts-ignore */
...(ngDevMode ? [{ debugName: "compareWith" }] : /* istanbul ignore next */ []));
/** Provide custom keyDown function. Executed before default handler. Return false to suppress execution of default key down handlers */
this._keyDownFn = input((_) => true, { ...(ngDevMode ? { debugName: "_keyDownFn" } : /* istanbul ignore next */ {}), alias: 'keyDownFn' });
this.keyDownFn = linkedSignal(() => this._keyDownFn(), /* @ts-ignore */
...(ngDevMode ? [{ debugName: "keyDownFn" }] : /* istanbul ignore next */ []));
/** @deprecated Has no effect: the CDK overlay renders in the native Popover API top layer automatically in supporting browsers. Will be removed in a future major version. */
this._popover = input(false, { ...(ngDevMode ? { debugName: "_popover" } : /* istanbul ignore next */ {}), alias: 'popover', transform: booleanAttribute });
/** @deprecated Has no effect: the CDK overlay renders in the native Popover API top layer automatically in supporting browsers. Will be removed in a future major version. */
this.popover = linkedSignal(() => this._popover(), /* @ts-ignore */
...(ngDevMode ? [{ debugName: "popover" }] : /* istanbul ignore next */ []));
// models
/** Object property to use for label. Default `label` */
this.bindLabel = model(undefined, /* @ts-ignore */
...(ngDevMode ? [{ debugName: "bindLabel" }] : /* istanbul ignore next */ []));
/** Object property to use for selected model. By default binds to whole object. */
this.bindValue = model(undefined, /* @ts-ignore */
...(ngDevMode ? [{ debugName: "bindValue" }] : /* istanbul ignore next */ []));
/** Allows to select dropdown appearance. Set to `outline` or `fill` for Material form-field styles (applies only to Material theme) */
this.appearance = model(undefined, /* @ts-ignore */
...(ngDevMode ? [{ debugName: "appearance" }] : /* istanbul ignore next */ []));
/** Allows manual control of dropdown opening and closing. `true` - won't close. `false` - won't open. */
this.isOpen = model(false, /* @ts-ignore */
...(ngDevMode ? [{ debugName: "isOpen" }] : /* istanbul ignore next */ []));
/** Items array */
this.items = model([], /* @ts-ignore */
...(ngDevMode ? [{ debugName: "items" }] : /* istanbul ignore next */ []));
// output events
/** Fired on select blur */
this.blurEvent = output({ alias: 'blur' });
/** Fired on select focus */
this.focusEvent = output({ alias: 'focus' });
/** Fired on model change. Outputs whole model */
this.changeEvent = output({ alias: 'change' });
/** Fired on select dropdown open */
this.openEvent = output({ alias: 'open' });
/** Fired on select dropdown close */
this.closeEvent = output({ alias: 'close' });
/** Fired while typing search term. Outputs search term with filtered items */
this.searchEvent = output({ alias: 'search' });
/** Fired on clear icon click */
this.clearEvent = output({ alias: 'clear' });
/** Fired when item is added while `[multiple]="true"`. Outputs added item */
this.addEvent = output({ alias: 'add' });
/** Fired when item is removed while `[multiple]="true"` */
this.removeEvent = output({ alias: 'remove' });
/** Fired when scrolled (only when `[virtualScroll]="true"`). Provides the start and end index of the currently available items. Can be used for loading more items in chunks before the user has scrolled all the way to the bottom of the list. */
this.scroll = output({ alias: 'scroll' });
/** Fired when scrolled to the end of items. Can be used for loading more items in chunks. */
this.scrollToEnd = output({ alias: 'scrollToEnd' });
// computed
this.disabled = computed(() => this.readonly() || this._disabled(), /* @ts-ignore */
...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
this.clearSearchOnAddValue = computed(() => {
if (isDefined(this.clearSearchOnAdd())) {
return this.clearSearchOnAdd();
}
if (isDefined(this.config.clearSearchOnAdd)) {
return this.config.clearSearchOnAdd;
}
return this.closeOnSelect();
}, /* @ts-ignore */
...(ngDevMode ? [{ debugName: "clearSearchOnAddValue" }] : /* istanbul ignore next */ []));
this.deselectOnClickValue = computed(() => {
if (isDefined(this.deselectOnClick())) {
return this.deselectOnClick();
}
if (isDefined(this.config.deselectOnClick)) {
return this.config.deselectOnClick;
}
return this.multiple();
}, /* @ts-ignore */
...(ngDevMode ? [{ debugName: "deselectOnClickValue" }] : /* istanbul ignore next */ []));
// content child queries
this.optionTemplate = contentChild(NgOptionTemplateDirective, { ...(ngDevMode ? { debugName: "optionTemplate" } : /* istanbul ignore next */ {}), read: TemplateRef });
this.optgroupTemplate = contentChild(NgOptgroupTemplateDirective, { ...(ngDevMode ? { debugName: "optgroupTemplate" } : /* istanbul ignore next */ {}), read: TemplateRef });
this.labelTemplate = contentChild(NgLabelTemplateDirective, { ...(ngDevMode ? { debugName: "labelTemplate" } : /* istanbul ignore next */ {}), read: TemplateRef });
this.multiLabelTemplate = contentChild(NgMultiLabelTemplateDirective, { ...(ngDevMode ? { debugName: "multiLabelTemplate" } : /* istanbul ignore next */ {}), read: TemplateRef });
this.headerTemplate = contentChild(NgHeaderTemplateDirective, { ...(ngDevMode ? { debugName: "headerTemplate" } : /* istanbul ignore next */ {}), read: TemplateRef });
this.footerTemplate = contentChild(NgFooterTemplateDirective, { ...(ngDevMode ? { debugName: "footerTemplate" } : /* istanbul ignore next */ {}), read: TemplateRef });
this.notFoundTemplate = contentChild(NgNotFoundTemplateDirective, { ...(ngDevMode ? { debugName: "notFoundTemplate" } : /* istanbul ignore next */ {}), read: TemplateRef });
this.placeholderTemplate = contentChild(NgPlaceholderTemplateDirective, { ...(ngDevMode ? { debugName: "placeholderTemplate" } : /* istanbul ignore next */ {}), read: TemplateRef });
this.typeToSearchTemplate = contentChild(NgTypeToSearchTemplateDirective, { ...(ngDevMode ? { debugName: "typeToSearchTemplate" } : /* istanbul ignore next */ {}), read: TemplateRef });
this.loadingTextTemplate = contentChild(NgLoadingTextTemplateDirective, { ...(ngDevMode ? { debugName: "loadingTextTemplate" } : /* istanbul ignore next */ {}), read: TemplateRef });
this.tagTemplate = contentChild(NgTagTemplateDirective, { ...(ngDevMode ? { debugName: "tagTemplate" } : /* istanbul ignore next */ {}), read: TemplateRef });
this.loadingSpinnerTemplate = contentChild(NgLoadingSpinnerTemplateDirective, { ...(ngDevMode ? { debugName: "loadingSpinnerTemplate" } : /* istanbul ignore next */ {}), read: TemplateRef });
this.clearButtonTemplate = contentChild(NgClearButtonTemplateDirective, { ...(ngDevMode ? { debugName: "clearButtonTemplate" } : /* istanbul ignore next */ {}), read: TemplateRef });
this.ngOptions = contentChildren(NgOptionComponent, { ...(ngDevMode ? { debugName: "ngOptions" } : /* istanbul ignore next */ {}), descendants: true });
// view children queries
this.dropdownPanel = viewChild(forwardRef(() => NgDropdownPanelComponent), /* @ts-ignore */
...(ngDevMode ? [{ debugName: "dropdownPanel" }] : /* istanbul ignore next */ []));
this.searchInput = viewChild('searchInput', /* @ts-ignore */
...(ngDevMode ? [{ debugName: "searchInput" }] : /* istanbul ignore next */ []));
this.clearButton = viewChild('clearButton', /* @ts-ignore */
...(ngDevMode ? [{ debugName: "clearButton" }] : /* istanbul ignore next */ []));
this._dropdownTemplate = viewChild('dropdownTemplate', /* @ts-ignore */
...(ngDevMode ? [{ debugName: "_dropdownTemplate" }] : /* istanbul ignore next */ []));
// The portal's embedded view is anchored at the template's own container so it stays
// part of this component's logical view tree (DI, view queries, detectChanges) while
// its DOM nodes live in the CDK overlay
this._dropdownOutlet = viewChild('dropdownTemplate', { ...(ngDevMode ? { debugName: "_dropdownOutlet" } : /* istanbul ignore next */ {}), read: ViewContainerRef });
this._selectContainer = viewChild('selectContainer', /* @ts-ignore */
...(ngDevMode ? [{ debugName: "_selectContainer" }] : /* istanbul ignore next */ []));
// public variables
this.dropdownId = newId();
/** Width of the notched-outline gap for the floated label in the material outline appearance */
this.outlineNotchWidth = signal(0, /* @ts-ignore */
...(ngDevMode ? [{ debugName: "outlineNotchWidth" }] : /* istanbul ignore next */ []));
this.viewPortItems = [];
this.tabFocusOnClear = signal(true, /* @ts-ignore */
...(ngDevMode ? [{ debugName: "tabFocusOnClear" }] : /* istanbul ignore next */ []));
this._cd = inject(ChangeDetectorRef);
this._console = inject(ConsoleService);
this._destroyRef = inject(DestroyRef);
this.autoFocus = inject(new HostAttributeToken('autofocus'), { optional: true });
/** Overlay hosting the dropdown panel while open. Bound into the panel so it can request repositioning. */
this.dropdownOverlayRef = null;
// private variables
this._defaultLabel = 'label';
this._editableSearchTermActive = computed(() => this.editableSearchTerm() && !this.multiple(), /* @ts-ignore */
...(ngDevMode ? [{ debugName: "_editableSearchTermActive" }] : /* istanbul ignore next */ []));
this._document = inject(DOCUMENT);
this._injector = inject(Injector);
this._isComposing = false;
this._keyPress$ = new Subject();
this._pressedKeys = [];
/** Last value written via `writeValue` or emitted via `_updateNgModel`; re-applied when mode/bindings change. */
this._lastWrittenValue = null;
this._searchTerm = signal(null, /* @ts-ignore */
...(ngDevMode ? [{ debugName: "_searchTerm" }] : /* istanbul ignore next */ []));
this._validTerm = computed(() => {
const term = this._searchTerm()?.trim();
return term && term.length >= this.minTermLength();
}, /* @ts-ignore */
...(ngDevMode ? [{ debugName: "_validTerm" }] : /* istanbul ignore next */ []));
/** Consumer classes on the host from static `class` and `[class]` bindings. */
this._consumerHostClasses = signal(null, /* @ts-ignore */
...(ngDevMode ? [{ debugName: "_consumerHostClasses" }] : /* istanbul ignore next */ []));
/** Classes forwarded to the overlay panel (mirrored host classes + `panelClass`). */
this.panelClasses = computed(() => {
return mergeClassValues(
// Preserve explicitly declared static classes even when they share a name with
// a framework-managed class that is otherwise filtered from the observed host.
this.classes, this._consumerHostClasses(),
// Keep supporting `[ngClass]` when it binds only to this component input and
// no NgClass directive is present in the consumer's template scope.
this.ngClass(), this.panelClass());
}, /* @ts-ignore */
...(ngDevMode ? [{ debugName: "panelClasses" }] : /* istanbul ignore next */ []));
this.clearItem = (item) => {
const option = this.selectedItems.find((x) => x.value === item);
this.unselect(option);
};
this.trackByOption = (_, item) => {
if (this.trackByFn()) {
return this.trackByFn()(item.value);
}
return item;
};
this._onChange = (_) => { };
this._onTouched = () => { };
const config = this.config;
const newSelectionModel = inject(SELECTION_MODEL_FACTORY, { optional: true });
const _elementRef = inject(ElementRef);
this._mergeGlobalConfig(config);
this.itemsList = new ItemsList(this, newSelectionModel ? newSelectionModel() : DefaultSelectionModelFactory());
this.element = _elementRef.nativeElement;
this._dropdownOverlay = new DropdownOverlayManager(this._injector, this._document, this.element);
this._handleSignalChanges();
afterEveryRender({
read: () => this._measureOutlineNotch(),
});
afterEveryRender({
read: () => {
const consumerClasses = readConsumerHostClasses(this.element);
const normalized = consumerClasses || null;
if (normalized !== this._consumerHostClasses()) {
this._consumerHostClasses.set(normalized);
}
},
});
this._destroyRef.onDestroy(() => {
this._dropdownOverlay.destroy();
this.dropdownOverlayRef = null;
});
}
/**
* Measures the placeholder label so the notched outline can leave a real gap in the border
* for the floated label instead of masking it with an opaque background (material theme).
* The 0.75 factor matches the `scale(0.75)` the material theme applies to the floated label.
*
* @since 23.2.3
*/
_measureOutlineNotch() {
if (this.appearance() !== 'outline') {
return;
}
const label = this.element.querySelector('.ng-select-container > .ng-value-container .ng-placeholder');
const width = label ? label.offsetWidth * 0.75 : 0;
if (width !== this.outlineNotchWidth()) {
this.outlineNotchWidth.set(width);
}
}
/**
* Gets whether the component currently owns focus.
*
* @returns The focused.
*
* @since 20.4.2
*/
get focused() {
return this._focused;
}
/**
* Gets whether search filtering or text composition is active.
*
* @returns The filtered.
*
* @since 3.0.0
*/
get filtered() {
return (!!this.searchTerm && this.searchable()) || this._isComposing;
}
/**
* Gets the search term.
*
* @returns The search term.
*
* @since 20.4.2
*/
get searchTerm() {
return this._searchTerm();
}
/**
* Gets the selected items.
*
* @returns The selected items.
*
* @since 3.0.0
*/
get selectedItems() {
return this.itemsList.selectedItems;
}
/**
* Gets the selected values.
*
* @returns The selected values.
*
* @since 3.0.0
*/
get selectedValues() {
return this.selectedItems.map((x) => x.value);
}
/**
* Determines whether the component has selected values.
*
* @returns Whether has value.
*
* @since 3.0.0
*/
get hasValue() {
return this.selectedItems.length > 0;
}
/**
* Gets the current panel position.
*
* @returns The current panel position.
*
* @since 3.0.0
*/
get currentPanelPosition() {
if (this.dropdownPanel()) {
return this.dropdownPanel().currentPosition;
}
return undefined;
}
/**
* Determines whether the add-tag option should be displayed.
*
* @returns Whether show add tag.
*
* @since 3.0.0
*/
get showAddTag() {
if (!this._validTerm()) {
return false;
}
const term = this.searchTerm.toLowerCase().trim();
return (this.addTag() &&
!this.itemsList.filteredItems.some((x) => x.label.toLowerCase() === term) &&
((!this.hideSelected() && this.isOpen()) || !this.selectedItems.some((x) => x.label.toLowerCase() === term)) &&
!this.loading());
}
/**
* Initializes the instance after Angular has assigned its inputs.
*
* @since 3.0.0
*/
ngOnInit() {
this._handleKeyPresses();
this._setInputAttributes();
this._warnDeprecatedInputs();
}
/**
* Responds to Angular input changes.
*
* @param changes - The changed Angular inputs.
*
* @since 3.0.0
*/
ngOnChanges(changes) {
const itemsChange = changes.items;
const isOpenChange = changes.isOpen;
const groupByChange = changes._groupBy ?? changes.groupBy;
if (itemsChange?.firstChange) {
this._itemsAreUsed = true;
this._setItems(itemsChange.currentValue || []);
}
if (isOpenChange) {
this._manualOpen = isDefined(isOpenChange.currentValue);
}
if (groupByChange?.firstChange && !itemsChange) {
this._setItems([...this.items()]);
}
this._setTabFocusOnClear();
}
/**
* Completes initialization that depends on rendered view children.
*
* @since 3.0.0
*/
ngAfterViewInit() {
if (!this._itemsAreUsed) {
this._setItemsFromNgOptions();
}
if (isDefined(this.autoFocus)) {
this.focus();
}
}
/**
* Routes a keyboard event to the appropriate ng-select interaction handler.
*
* @param $event - The DOM event to process.
*
* @since 3.0.0
*/
handleKeyDown($event) {
const keyName = $event.key;
if (Object.values(KeyCode).includes(keyName)) {
if (this.keyDownFn()($event) === false) {
return;
}
this.handleKeyCode($event);
}
else if (keyName && keyName.length === 1) {
this._keyPress$.next(keyName.toLocaleLowerCase());
}
}
/**
* Handles navigation and selection key codes.
*
* @param $event - The DOM event to process.
*
* @since 3.1.1
*/
handleKeyCode($event) {
const target = $event.target;
if (this.clearButton() && this.clearButton().nativeElement === target) {
this.handleKeyCodeClear($event);
}
else {
this.handleKeyCodeInput($event);
}
}
/**
* Handles keyboard interaction originating from the search input.
*
* @param $event - The DOM event to process.
*
* @since 11.1.2
*/
handleKeyCodeInput($event) {
switch ($event.key) {
case KeyCode.ArrowDown:
this._handleArrowDown($event);
break;
case KeyCode.ArrowUp:
this._handleArrowUp($event);
break;
case KeyCode.Space:
this._handleSpace($event);
break;
case KeyCode.Enter:
this._handleEnter($event);
break;
case KeyCode.Tab:
this._handleTab($event);
break;
case KeyCode.Esc:
// Only consume Escape when it closes the dropdown so parent overlays/dialogs can still handle Escape when already closed
if (!this.isOpen() || this._manualOpen) {
return;
}
this.close();
$event.preventDefault();
break;
case KeyCode.Backspace:
this._handleBackspace();
break;
}
}
/**
* Handles keyboard interaction on the clear control.
*
* @param $event - The DOM event to process.
*
* @since 11.1.2
*/
handleKeyCodeClear($event) {
switch ($event.key) {
case KeyCode.Enter:
case KeyCode.Space:
this.handleClearClick();
$event.preventDefault();
break;
}
}
/**
* Handles keyboard removal of a selected value.
*
* @param $event - The DOM event to process.
* @param item - The option to process.
*
* @since 23.3.0
*/
handleRemoveKeydown($event, item) {
if (item.disabled) {
return;
}
if ($event.key === KeyCode.Enter || $event.key === KeyCode.Space) {
$event.preventDefault();
$event.stopPropagation();
this.unselect(item);
}
}
/**
* Handles pointer interaction within the select container.
*
* @param $event - The DOM event to process.
*
* @since 3.0.0
*/
handleMousedown($event) {
if (this.disabled()) {
return;
}
if (this.preventToggleOnRightClick() && $event.button === 2) {
return false;
}
const target = $event.target;
// Allow highlighting/copying the selected label when search is disabled.
// Skipping preventDefault + toggle lets the browser start a text selection.
// See https://github.com/ng-select/ng-select/issues/2669
const onSelectedValue = !!target.closest?.('.ng-value');
const onValueIcon = target.classList.contains('ng-value-icon') || !!target.closest?.('.ng-value-icon');
if (!this.searchable() && onSelectedValue && !onValueIcon) {
if (!this._focused) {
this.focus();
}
return;
}
// Skip during Angular event replay (SSR hydration): preventDefault has no effect
// after browser dispatch and throws — see https://github.com/ng-select/ng-select/issues/2549
if (target.tagName !== 'INPUT' && $event.eventPhase !== EventPhase.REPLAY) {
$event.preventDefault();
}
if (target.classList.contains('ng-clear-wrapper')) {
// Don't handle clear on mousedown - let click event handle it
return;
}
if (target.classList.contains('ng-arrow-wrapper')) {
this.handleArrowClick();
return;
}
if (target.classList.contains('ng-value-icon')) {
return;
}
if (!this._focused) {
this.focus();
}
if (this.searchable()) {
this.open();
}
else {
this.toggle();
}
}
/**
* Toggles the dropdown from the arrow control.
*
* @since 3.0.0
*/
handleArrowClick() {
if (this.isOpen()) {
this.close();
}
else {
this.open();
}
}
/**
* Clears the model from a pointer interaction.
*
* @param _event - The DOM event to process.
*
* @since 3.0.0
*/
handleClearClick(_event) {
if (this.hasValue) {
this.itemsList.clearSelected(this.clearKeepsDisabledOptions());
this._updateNgModel();
}
this._clearSearch();
this.focus();
this.clearEvent.emit();
this._onSelectionChanged();
}
/**
* Clears the selected model values and resets the search state.
*
* @since 3.0.0
*/
clearModel() {
if (!this.clearable()) {
return;
}
this.itemsList.clearSelected(false);
this._updateNgModel();
}
/**
* Writes a ControlValueAccessor model value into the component.
*
* @param value - The value to process.
*
* @since 3.0.0
*/
writeValue(value) {
this._lastWrittenValue = value;
this.itemsList.clearSelected(false);
this._handleWriteValue(value);
if (this._editableSearchTermActive()) {
this._setSearchTermFromItems();
}
this._cd.markForCheck();
}
/**
* Registers the ControlValueAccessor model-change callback.
*
* @param fn - The fn.
*
* @since 3.0.0
*/
registerOnChange(fn) {
this._onChange = fn;
}
/**
* Registers the ControlValueAccessor touched callback.
*
* @param fn - The fn.
*
* @since 3.0.0
*/
registerOnTouched(fn) {
this._onTouched = fn;
}
/**
* Updates the disabled state supplied by Angular forms.
*
* @param state - The state.
*
* @since 3.0.0
*/
setDisabledState(state) {
this._disabled.set(state);
this._cd.markForCheck();
}
/**
* Toggles the dropdown panel.
*
* @since 3.0.0
*/
toggle() {
if (!this.isOpen()) {
this.open();
}
else {
this.close();
}
}
/**
* Opens the select dropdown panel
*
* @since 3.0.0
*/
open() {
if (this.disabled() || this.isOpen() || this._manualOpen) {
return;
}
if (!this.typeahead()?.observed && !this.addTag() && this.itemsList.noItemsToSelect) {
return;
}
this.isOpen.set(true);
this.itemsList.markSelectedOrDefault(this.markFirst());
this.openEvent.emit();
if (!this.searchTerm) {
this.focus();
}
// Attach synchronously (effects only flush on the next tick): consumers expect the
// panel to be in the DOM right after open() returns
this._syncDropdownOverlay();
this.detectChanges();
}
/**
* Closes the select dropdown panel
*
* @since 3.0.0
*/
close() {
if (!this.isOpen() || this._manualOpen) {
return;
}
this.isOpen.set(false);
this._isComposing = false;
if (!this._editableSearchTermActive()) {
this._clearSearch();
}
else {
this.itemsList.resetFilteredItems();
}
this.itemsList.unmarkItem();
this._onTouched();
this.closeEvent.emit();
// Detach synchronously (effects only flush on the next tick): the panel must leave
// the DOM without relying on zone-triggered change detection (#2765)
this._syncDropdownOverlay();
this.detectChanges();
}
/**
* Selects or unselects an option based on its current state.
*
* @param item - The option to process.
*
* @since 3.0.0
*/
toggleItem(item) {
if (!item || item.disabled || this.disabled()) {
return;
}
if (this.deselectOnClickValue() && item.selected) {
this.unselect(item);
}
else {
this.select(item);
}
if (this._editableSearchTermActive()) {
this._setSearchTermFromItems();
}
}
/**
* Selects an option according to the active selection mode.
*
* @param item - The option to process.
*
* @since 3.0.0
*/
select(item) {
if (!item.selected) {
this.itemsList.select(item);
if (this.clearSearchOnAddValue() && !this._editableSearchTermActive()) {
this._clearSearch();
}
this._updateNgModel();
if (this.multiple()) {
this.addEvent.emit(item.value);
}
}
if (this.closeOnSelect() || this.itemsList.noItemsToSelect) {
this.close();
}
this._onSelectionChanged();
}
/**
* Focuses the select element
*
* @since 3.0.0
*/
focus() {
this.searchInput().nativeElement.focus();
}
/**
* Blurs the select element
*
* @since 3.0.0
*/
blur() {
this.searchInput().nativeElement.blur();
}
/**
* Removes an option from the current selection.
*
* @param item - The option to process.
*
* @since 3.0.0
*/
unselect(item) {
if (!item || this.disabled() || item.disabled) {
return;
}
this.itemsList.unselect(item);
this.focus();
this._updateNgModel();
this.removeEvent.emit(item.value);
this._onSelectionChanged();
}
/**
* Creates and selects an option from the current search term.
*
* @since 3.0.0
*/
selectTag() {
let tag;
if (isFunction(this.addTag())) {
tag = this.addTag()(this.searchTerm);
}
else {
tag = this._primitive ? this.searchTerm : { [this.bindLabel()]: this.searchTerm };
}
const handleTag = (item) => (this.typeahead()?.observed || !this.isOpen() ? this.itemsList.mapItem(item, null) : this.itemsList.addItem(item));
if (isPromise(tag)) {
tag.then((item) => this.select(handleTag(item))).catch(() => { });
}
else if (tag) {
this.select(handleTag(tag));
}
}
/**
* Determines whether the clear control should be displayed.
*
* @since 3.0.0
*/
showClear() {
return this.clearable() && (this.hasValue || this.searchTerm) && !this.disabled();
}
/**
* Moves keyboard focus to the clear control.
*
* @since 11.1.2
*/
focusOnClear() {
this.blur();
if (this.clearButton()) {
this.clearButton().nativeElement.focus();
}
}
/**
* Determines whether the empty-results message should be displayed.
*
* @since 3.0.0
*/
showNoItemsFound() {
const empty = this.itemsList.filteredItems.length === 0;
return (((empty && !this.typeahead()?.observed && !this.loading()) || (empty && this.typeahead()?.observed && this._validTerm() && !this.loading())) &&
!this.showAddTag);
}
/**
* Determines whether the type-to-search prompt should be displayed.
*
* @since 3.0.0
*/
showTypeToSearch() {
const empty = this.itemsList.filteredItems.length === 0;
return empty && this.typeahead()?.observed && !this._validTerm() && !this.loading();
}
/**
* Marks the start of an input method editor composition.
*
* @since 3.0.5
*/
onCompositionStart() {
this._isComposing = true;
}
/**
* Finishes input method editor composition and applies the search term.
*
* @param term - The term.
*
* @since 3.0.5
*/
onCompositionEnd(term) {
this._isComposing = false;
if (this.searchWhileComposing()) {
return;
}
this.filter(term);
}
/**
* Filters the available options using the current search configuration.
*
* @param term - The term.
*
* @since 3.0.0
*/
filter(term) {
if (this._isComposing && !this.searchWhileComposing()) {
return;
}
this._searchTerm.set(term);
if (this.typeahead()?.observed && (this._validTerm() || this.minTermLength() === 0)) {
this.typeahead().next(term);
}
if (!this.typeahead()?.observed) {
this.itemsList.filter(term);
if (this.isOpen()) {
this.itemsList.markSelectedOrDefault(this.markFirst());
}
}
this.searchEvent.emit({ term, items: this.itemsList.filteredItems.map((x) => x.value) });
this.open();
}
/**
* Handles focus entering the search input.
*
* @param $event - The DOM event to process.
*
* @since 3.0.0
*/
onInputFocus($event) {
if (this._focused) {
return;
}
if (this._editableSearchTermActive()) {
this._setSearchTermFromItems();
}
this.element.classList.add('ng-select-focused');
this.focusEvent.emit($event);
this._focused = true;
}
/**
* Handles focus leaving the search input.
*
* @param $event - The DOM event to process.
*
* @since 3.0.0
*/
onInputBlur($event) {
this.element.classList.remove('ng-select-focused');
this.blurEvent.emit($event);
if (!this.isOpen() && !this.disabled()) {
this._onTouched();
}
if (this._editableSearchTermActive()) {
this._setSearchTermFromItems();
}
this._focused = false;
}
/**
* Marks an option when pointer interaction hovers it.
*
* @param item - The option to process.
*
* @since 3.0.0
*/
onItemHover(item) {
if (item.disabled) {
return;
}
this.itemsList.markItem(item);
}
/**
* Requests an OnPush change-detection pass.
*
* @since 3.0.0
*/
detectChanges() {
if (!this._cd.destroyed) {
this._cd.detectChanges();
}
}
/**
* Registers effects that synchronize signal-based inputs with component state.
*
* @since 23.0.4
*/
_handleSignalChanges() {
let itemsInitialized = false;
effect(() => {
const items = this.items();
if (!itemsInitialized) {
itemsInitialized = true;
return;
}
untracked(() => {
this._itemsAreUsed = true;
this._setItems(items || []);
});
}, { injector: this._injector });
let multipleInitialized = false;
effect(() => {
this.multiple();
if (!multipleInitialized) {
multipleInitialized = true;
return;
}
untracked(() => {
const written = this._lastWrittenValue;
this._resyncSelectionFromWrittenValue();
const hadValue = isDefined(written) && !(Array.isArray(written) && written.length === 0);
if (hadValue && !this.hasValue) {
// The retained value cannot be represented in the new mode; emit the cleared value
// so the form model and the UI stay in sync
this._updateNgModel();
}
});
}, { injector: this._injector });
// Re-map the retained form value when the value binding or comparator changes.
// bindLabel is intentionally not tracked: _setItems writes it and would loop.
let bindingsInitialized = false;
effect(() => {
this.bindValue();
this.compareWith();
if (!bindingsInitialized) {
bindingsInitialized = true;
return;
}
untracked(() => this._resyncSelectionFromWrittenValue());
}, { injector: this._injector });
let groupByInitialized = false;
effect(() => {
this.groupBy();
if (!groupByInitialized) {
groupByInitialized = true;
return;
}
untracked(() => this._setItems([...this.items()]));
}, { injector: this._injector });
effect(() => {
this.inputAttrs();
const input = this.searchInput();
if (!input) {
return;
}
untracked(() => this._setInputAttributes());
}, { injector: this._injector });
// open()/close() sync the overlay imperatively; this effect covers the paths that
// bypass them — an [isOpen] binding (manual mode), programmatic isOpen.set(), and
// dropdownPosition changes while open
effect(() => {
this.isOpen();
this.dropdownPosition();
// Track the template query too: with [isOpen] pre-set, the first run can happen
// before view children resolve, and this re-runs the effect once they do
this._dropdownTemplate();
untracked(() => this._syncDropdownOverlay());
}, { injector: this._injector });
}
/**
* Restores the editable search term from the selected option.
*
* @since 3.7.3
*/
_setSearchTermFromItems() {
const selected = this.selectedItems?.[0];
this._searchTerm.set(selected?.label ?? null);
}
/**
* Re-applies the last written form value against the current mode and bindings.
*
* @since 24.0.5
*/
_resyncSelectionFromWrittenValue() {
this.itemsList.clearSelected(false);
this._handleWriteValue(this._lastWrittenValue);
if (this._editableSearchTermActive()) {
this._setSearchTermFromItems();
}
this._cd.markForCheck();
}
/**
* Maps the bound items collection into the internal option list.
*
* @param items - The options to process.
*
* @since 3.0.0
*/
_setItems(items) {
const firstItem = items[0];
this.bindLabel.set(this.bindLabel() || this._defaultLabel);
this._primitive = isDefined(firstItem) ? !isObject(firstItem) : this._primitive || this.bindLabel() === this._defaultLabel;
this.itemsList.setItems(items);
if (items.length > 0 && this.hasValue) {
this.itemsList.mapSelectedItems();
}
if (this.isOpen() && isDefined(this.searchTerm) && !this.typeahead()?.observed) {
this.itemsList.filter(this.searchTerm);
}
if (this.typeahead()?.observed || this.isOpen()) {
this.itemsList.markSelectedOrDefault(this.markFirst());
}
}
/**
* Maps projected `ng-option` components into the internal option list.
*
* @since 3.0.0
*/
_setItemsFromNgOptions() {
effect(() => {
const options = this.ngOptions();
// Wait until all ng-option inputs are initialized (avoids _groupBy crash when values load async)
if (options.length > 0 && !options.every((opt) => opt.isInitialized())) {
return;
}
this.bindLabel.set(this._defaultLabel);
const items = options.map((option) => ({
$ngOptionValue: option.value(),
$ngOptionLabel: option.label(),
$ngOptionClasses: option.classes(),
disabled: option.disabled(),
}));
this.items.set(items);
this.itemsList.setItems(items);
if (this.hasValue) {
this.itemsList.mapSelectedItems();
}
this._cd.detectChanges();
options
// find item for each option
.map((option) => ({
option,
item: this.itemsList.findItem(option.value()),
}))
// filter non found items
.filter(({ item }) => isDefined(item))
// process to update disabled and label
.forEach(({ option, item }) => {
item.disabled = option.disabled();
item.label = option.label() || item.label;
item.classes = option.classes();
});
}, { injector: this._injector });
}
/**
* Maps a valid external model value into selected options.
*
* @param value - The form value.
*
* @since 3.0.0
*/
_handleWriteValue(value) {
const validation = validateWriteValue(value, {
bindValue: this.bindValue(),
compareWith: this.compareWith(),
multiple: this.multiple(),
});
if (!validation.valid) {
if (validation.warning) {
this._console.warn(validation.warning);
}
return;
}
const select = (val) => {
let item = this.itemsList.findItem(val);
if (item) {
this.itemsList.select(item);
}
else {
item = createUnmatchedOptionValue(val, this.bindLabel() || this._defaultLabel, this.bindValue());
this.itemsList.select(this.itemsList.mapItem(item, null));
}
};
if (this.multiple()) {
value.forEach((item) => select(item));
}
else {
select(value);
}
}
/**
* Matches typed characters to options for keyboard navigation.
*
* @since 3.0.0
*/
_handleKeyPresses() {
if (this.searchable()) {
return;
}
this._keyPress$
.pipe(takeUntilDestroyed(this._destroyRef), tap((letter) => this._pressedKeys.push(letter)), debounceTime(200), filter(() => this._pressedKeys.length > 0), map(() => this._pressedKeys.join('')))
.subscribe((term) => {
const item = this.itemsList.findByLabel(term);
if (item) {
if (this.isOpen()) {
if (!item.disabled) {
this.itemsList.markItem(item);
this._scrollToMarked();
// Required under zoneless CD: this subscription fires from a debounce
// timer, which schedules nothing by itself
this._cd.markForCheck();
}
}
else {
this.select(item);
}
}
this._pressedKeys = [];
});
}
/**
* Applies configured attributes to the native search input.
*
* @since 3.0.0
*/
_setInputAttributes() {
const input = this.searchInput().nativeElement;
const attributes = {
type: 'text',
autocorrect: 'off',
autocapitalize: 'off',
autocomplete: 'off',
...this.inputAttrs(),
};
for (const key of Object.keys(attributes)) {
input.setAttribute(key, attributes[key]);
}
}
/**
* Updates whether Tab should move focus to the clear control.
*
* @since 15.1.0
*/
_setTabFocusOnClear() {
this.tabFocusOnClear.set(isDefined(this.tabFocusOnClearButton()) ? !!this.tabFocusOnClearButton() : this.config.tabFocusOnClear);
}
/**
* Builds and emits the current ControlValueAccessor model value.
*
* @since 3.0.0
*/
_updateNgModel() {
const model = selectedItemsToModel(this.selectedItems, {
bindValue: this.bindValue(),
groupBy: this.groupBy(),
hasGroupValue: !!this.groupValue(),
}, (value, key) => this.itemsList.resolveNested(value, key));
const selected = this.selectedItems.map((x) => x.value);
if (this.multiple()) {
this._lastWrittenValue = model;
this._onChange(model);
this.changeEvent.emit(selected);
}
else {
const single = isDefined(model[0]) ? model[0] : null;
this._lastWrittenValue = single;
this._onChange(single);
this.changeEvent.emit(selected[0]);
}
// Required under zoneless CD: sole notifier for the programmatic selection
// APIs (select/unselect/clearModel/clearItem) called from non-Angular contexts
this._cd.markForCheck();
}
/**
* Clears the current search term when configuration permits it.
*
* @since 3.0.0
*/
_clearSearch() {
if (!this.searchTerm) {
return;
}
this._changeSearch(null);
this.itemsList.resetFilteredItems();
}
/**
* Updates the search term and emits the search event.
*
* @param searchTerm - The search term.
*
* @since 3.0.6
*/
_changeSearch(searchTerm) {
this._searchTerm.set(searchTerm);
}
/**
* Scrolls the dropdown to the currently marked option.
*
* @since 3.0.0
*/
_scrollToMarked() {
if (!this.isOpen() || !this.dropdownPanel()) {
return;
}
this.dropdownPanel().scrollTo(this.itemsList.markedItem);
}
/**
* Scrolls the dropdown to the add-tag option.
*
* @since 3.0.0
*/
_scrollToTag() {
if (!this.isOpen() || !this.dropdownPanel()) {
return;
}
this.dropdownPanel().scrollToTag();
}
/**
* Synchronizes search and close behavior after selection changes.
*
* @since 3.0.0
*/
_onSelectionChanged() {
if (this.isOpen() && this.deselectOnClickValue()) {
// Make sure items are rendered.
this._cd.detectChanges();
this.dropdownPanel()?.adjustPosition();
}
}
/**
* Attaches or detaches the dropdown overlay to match the current open state. Idempotent.
*
* @since 23.7.0
*/
_syncDropdownOverlay() {
const template = this._dropdownTemplate();
const viewContainer = this._dropdownOutlet();
if (!template || !viewContainer) {
return;
}
if (this.isOpen()) {
this._dropdownOverlay.open({
appendTo: this.appendTo() ?? this.config.appendTo ?? null,
beforeAttach: (overlayRef) => (this.dropdownOverlayRef = overlayRef),
origin: this._dropdownOrigin(),
position: this.dropdownPosition(),
template,
viewContainer,
});
}
else {
this._dropdownOverlay.close();
}
}
/**
* The dropdown anchors to the `.ng-select-container` box, not the host: themes may pad
* the host below the container (material reserves 1.25em of subscript space under the
* underline), and the panel must sit flush against the visible field — the same anchor
* the pre-overlay `appendTo` positioning used.
*
* @since 23.7.0
*/
_dropdownOrigin() {
return this._selectContainer()?.nativeElement ?? this.element;
}
/**
* Reports development-mode warnings for deprecated inputs.
*
* @since 23.7.0
*/
_warnDeprecatedInputs() {
if (!isDevMode()) {
return;
}
if (this.popover()) {
this._console.warn('[ng-select] `popover` is deprecated and has no effect: the dropdown panel now renders in an Angular CDK overlay, which uses the native Popover API top layer automatically in supporting browsers.');
}
}
/**
* Handles Tab selection, clear-control focus, and close behavior.
*
* @param $event - The DOM event to process.
*
* @since 3.0.0
*/
_handleTab($event) {
if (this.isOpen() === false) {
if (this.showClear() && !$event.shiftKey && this.tabFocusOnClear()) {
this.focusOnClear();
$event.preventDefault();
}
else if (!this.addTag()) {
return;
}
}
if (this.selectOnTab()) {
if (this.itemsList.markedItem) {
this.toggleItem(this.itemsList.markedItem);
$event.preventDefault();
}
else if (this.showAddTag) {
this.selectTag();
$event.preventDefault();
}
else {
this.close();
}
}
else {
this.close();
}
}
/**
* Handles Enter selection and dropdown opening.
*
* @param $event - The DOM event to process.
*
* @since 3.0.0
*/
_handleEnter($event) {
const openOnEnter = this.openOnEnter() ?? this.config.openOnEnter;
if (this.isOpen() || this._manualOpen) {
if (this.itemsList.markedItem) {
this.toggleItem(this.itemsList.markedItem);
}
else if (this.showAddTag) {
this.selectTag();
}
}
else if (openOnEnter) {
this.open();
}
else {
return;
}
$event.preventDefault();
}
/**
* Handles Space selection and dropdown opening.
*
* @param $event - The DOM event to process.
*
* @since 3.0.0
*/
_handleSpace($event) {
if (this.isOpen() || this._manualOpen) {
return;
}
this.open();
$event.preventDefault();
}
/**
* Moves the marked option down or opens the dropdown.
*
* @param $event - The DOM event to process.
*
* @since 3.0.0
*/
_handleArrowDown($event) {
if (this._nextItemIsTag(+1)) {
this.itemsList.unmarkItem();
this._scrollToTag();
}
else {
this.itemsList.markNextItem();
this._scrollToMarked();
}
this.open();
$event.preventDefault();
}
/**
* Moves the marked option up or opens the dropdown.
*
* @param $event - The DOM event to process.
*
* @since 3.0.0
*/
_handleArrowUp($event) {
if (!this.isOpen()) {
return;
}
if (this._nextItemIsTag(-1)) {
this.itemsList.unmarkItem();
this._scrollToTag();
}
else {
this.itemsList.markPreviousItem();
this._scrollToMarked();
}
$event.preventDefault();
}
/**
* Determines whether the next keyboard target is the add-tag option.
*
* @param nextStep - The next step.
* @returns Whether next item is tag.
*
* @since 3.0.0
*/
_nextItemIsTag(nextStep) {
const nextIndex = this.itemsList.markedIndex + nextStep;
return this.addTag() && this.searchTerm && this.itemsList.markedItem && (nextIndex < 0 || nextIndex === this.itemsList.filteredItems.length);
}
/**
* Removes the last selected value when Backspace clearing is enabled.
*
* @since 3.0.0
*/
_handleBackspace() {
if (this.searchTerm || !this.clearable() || !this.clearOnBackspace() || !this.hasValue) {
return;
}
if (this.multiple()) {
this.unselect(this.itemsList.lastSelectedItem);
}
else {
this.clearModel();
}
}
/**
* Applies configured global defaults that were not set locally.
*
* @param config - The global ng-select configuration.
*
* @since 3.0.0
*/
_mergeGlobalConfig(config) {
this.bindValue.set(this.bindValue() || config.bindValue);
this.bindLabel.set(this.bindLabel() || config.bindLabel || this._defaultLabel);
this.appearance.set(this.appearance() || config.appearance);
this._setTabFocusOnClear();
}
/**
* Gets virtual scroll value from input or from config
*
* @param config NgSelectConfig object
*
* @returns `true` if virtual scroll is enabled, `false` otherwise
*
* @since 14.7.0
*/
getVirtualScroll(config) {
return isDefined(this._virtualScroll()) ? this._virtualScroll() : this.isVirtualScrollDisabled(config);
}
/**
* Gets disableVirtualScroll value from input or from config
*
* @param config NgSelectConfig object
*
* @returns `true` if disableVirtualScroll is enabled, `false` otherwise
*
* @since 14.7.0
*/
isVirtualScrollDisabled(config) {
return isDefined(config.disableVirtualScroll) ? !config.disableVirtualScroll : false;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: NgSelectComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.4", type: NgSelectComponent, isStandalone: true, selector: "ng-select", inputs: { _ariaLabelDropdown: { classPropertyName: "_ariaLabelDropdown", publicName: "ariaLabelDropdown", isSignal: true, isRequired: false, transformFunction: null }, _ariaLabel: { classPropertyName: "_ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, _markFirst: { classPropertyName: "_markFirst", publicName: "markFirst", isSignal: true, isRequired: false, transformFunction: null }, _placeholder: { classPropertyName: "_placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, _fixedPlaceholder: { classPropertyName: "_fixedPlaceholder", publicName: "fixedPlaceholder", isSignal: true, isRequired: false, transformFunction: null }, _notFoundText: { classPropertyName: "_notFoundText", publicName: "notFoundText", isSignal: true, isRequired: false, transformFunction: null }, _typeToSearchText: { classPropertyName: "_typeToSearchText", publicName: "typeToSearchText", isSignal: true, isRequired: false, transformFunction: null }, _preventToggleOnRightClick: { classPropertyName: "_preventToggleOnRightClick", publicName: "preventToggleOnRightClick", isSignal: true, isRequired: false, transformFunction: null }, _addTagText: { classPropertyName: "_addTagText", publicName: "addTagText", isSignal: true, isRequired: false, transformFunction: null }, _loadingText: { classPropertyName: "_loadingText", publicName: "loadingText", isSignal: true, isRequired: false, transformFunction: null }, _clearAllText: { classPropertyName: "_clearAllText", publicName: "clearAllText", isSignal: true, isRequired: false, transformFunction: null }, _removeText: { classPropertyName: "_removeText", publicName: "removeText", isSignal: true, isRequired: false, transformFunction: null }, _dropdownPosition: { classPropertyName: "_dropdownPosition", publicName: "dropdownPosition", isSignal: true, isRequired: false, transformFunction: null }, _appendTo: { classPropertyName: "_appendTo", publicName: "appendTo", isSignal: true, isRequired: false, transformFunction: null }, _outsideClickEvent: { classPropertyName: "_outsideClickEvent", publicName: "outsideClickEvent", isSignal: true, isRequired: false, transformFunction: null }, _loading: { classPropertyName: "_loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, _closeOnSelect: { classPropertyName: "_closeOnSelect", publicName: "closeOnSelect", isSignal: true, isRequired: false, transformFunction: null }, _hideSelected: { classPropertyName: "_hideSelected", publicName: "hideSelected", isSignal: true, isRequired: false, transformFunction: null }, _selectOnTab: { classPropertyName: "_selectOnTab", publicName: "selectOnTab", isSignal: true, isRequired: false, transformFunction: null }, _openOnEnter: { classPropertyName: "_openOnEnter", publicName: "openOnEnter", isSignal: true, isRequired: false, transformFunction: null }, _maxSelectedItems: { classPropertyName: "_maxSelectedItems", publicName: "maxSelectedItems", isSignal: true, isRequired: false, transformFunction: null }, _groupBy: { classPropertyName: "_groupBy", publicName: "groupBy", isSignal: true, isRequired: false, transformFunction: null }, _groupValue: { classPropertyName: "_groupValue", publicName: "groupValue", isSignal: true, isRequired: false, transformFunction: null }, _bufferAmount: { classPropertyName: "_bufferAmount", publicName: "bufferAmount", isSignal: true, isRequired: false, transformFunction: null }, _virtualScroll: { classPropertyName: "_virtualScroll", publicName: "virtualScroll", isSignal: true, isRequired: false, transformFunction: null }, _selectableGroup: { classPropertyName: "_selectableGroup", publicName: "selectableGroup", isSignal: true, isRequired: false, transformFunction: null }, _tabFocusOnClearButton: { classPropertyName: "_tabFocusOnClearButton", publicName: "tabFocusOnClearButton", isSignal: true, isRequired: false, transformFunction: null }, _selectableGroupAsModel: { classPropertyName: "_selectableGroupAsModel", publicName: "selectableGroupAsModel", isSignal: true, isRequired: false, transformFunction: null }, _searchFn: { classPropertyName: "_searchFn", publicName: "searchFn", isSignal: true, isRequired: false, transformFunction: null }, _trackByFn: { classPropertyName: "_trackByFn", publicName: "trackByFn", isSignal: true, isRequired: false, transformFunction: null }, _clearOnBackspace: { classPropertyName: "_clearOnBackspace", publicName: "clearOnBackspace", isSignal: true, isRequired: false, transformFunction: null }, _labelForId: { classPropertyName: "_labelForId", publicName: "labelForId", isSignal: true, isRequired: false, transformFunction: null }, _inputAttrs: { classPropertyName: "_inputAttrs", publicName: "inputAttrs", isSignal: true, isRequired: false, transformFunction: null }, _tabIndex: { classPropertyName: "_tabIndex", publicName: "tabIndex", isSignal: true, isRequired: false, transformFunction: null }, _readonly: { classPropertyName: "_readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, _searchWhileComposing: { classPropertyName: "_searchWhileComposing", publicName: "searchWhileComposing", isSignal: true, isRequired: false, transformFunction: null }, _minTermLength: { classPropertyName: "_minTermLength", publicName: "minTermLength", isSignal: true, isRequired: false, transformFunction: null }, _editableSearchTerm: { classPropertyName: "_editableSearchTerm", publicName: "editableSearchTerm", isSignal: true, isRequired: false, transformFunction: null }, _ngClass: { classPropertyName: "_ngClass", publicName: "ngClass", isSignal: true, isRequired: false, transformFunction: null }, _panelClass: { classPropertyName: "_panelClass", publicName: "panelClass", isSignal: true, isRequired: false, transformFunction: null }, _typeahead: { classPropertyName: "_typeahead", publicName: "typeahead", isSignal: true, isRequired: false, transformFunction: null }, _multiple: { classPropertyName: "_multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, _addTag: { classPropertyName: "_addTag", publicName: "addTag", isSignal: true, isRequired: false, transformFunction: null }, _searchable: { classPropertyName: "_searchable", publicName: "searchable", isSignal: true, isRequired: false, transformFunction: null }, _clearable: { classPropertyName: "_clearable", publicName: "clearable", isSignal: true, isRequired: false, transformFunction: null }, _clearKeepsDisabledOptions: { classPropertyName: "_clearKeepsDisabledOptions", publicName: "clearKeepsDisabledOptions", isSignal: true, isRequired: false, transformFunction: null }, _deselectOnClick: { classPropertyName: "_deselectOnClick", publicName: "deselectOnClick", isSignal: true, isRequired: false, transformFunction: null }, _clearSearchOnAdd: { classPropertyName: "_clearSearchOnAdd", publicName: "clearSearchOnAdd", isSignal: true, isRequired: false, transformFunction: null }, _compareWith: { classPropertyName: "_compareWith", publicName: "compareWith", isSignal: true, isRequired: false, transformFunction: null }, _keyDownFn: { classPropertyName: "_keyDownFn", publicName: "keyDownFn", isSignal: true, isRequired: false, transformFunction: null }, _popover: { classPropertyName: "_popover", publicName: "popover", isSignal: true, isRequired: false, transformFunction: null }, bindLabel: { classPropertyName: "bindLabel", publicName: "bindLabel", isSignal: true, isRequired: false, transformFunction: null }, bindValue: { classPropertyName: "bindValue", publicName: "bindValue", isSignal: true, isRequired: false, transformFunction: null }, appearance: { classPropertyName: "appearance", publicName: "appearance", isSignal: true, isRequired: false, transformFunction: null }, isOpen: { classPropertyName: "isOpen", publicName: "isOpen", isSignal: true, isRequired: false, transformFunction: null }, items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { bindLabel: "bindLabelChange", bindValue: "bindValueChange", appearance: "appearanceChange", isOpen: "isOpenChange", items: "itemsChange", blurEvent: "blur", focusEvent: "focus", changeEvent: "change", openEvent: "open", closeEvent: "close", searchEvent: "search", clearEvent: "clear", addEvent: "add", removeEvent: "remove", scroll: "scroll", scrollToEnd: "scrollToEnd" }, host: { listeners: { "keydown": "handleKeyDown($event)" }, properties: { "class.ng-select": "true", "class.ng-select-single": "!multiple()", "class.ng-select-typeahead": "typeahead()", "class.ng-select-multiple": "multiple()", "class.ng-select-taggable": "addTag()", "class.ng-select-searchable": "searchable()", "class.ng-select-clearable": "clearable()", "class.ng-select-opened": "isOpen()", "class.ng-select-filtered": "filtered", "class.ng-select-disabled": "disabled()" } }, providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => NgSelectComponent),
multi: true,
},
NgDropdownPanelService,
], queries: [{ propertyName: "optionTemplate", first: true, predicate: NgOptionTemplateDirective, descendants: true, read: TemplateRef, isSignal: true }, { propertyName: "optgroupTemplate", first: true, predicate: NgOptgroupTemplateDirective, descendants: true, read: TemplateRef, isSignal: true }, { propertyName: "labelTemplate", first: true, predicate: NgLabelTemplateDirective, descendants: true, read: TemplateRef, isSignal: true }, { propertyName: "multiLabelTemplate", first: true, predicate: NgMultiLabelTemplateDirective, descendants: true, read: TemplateRef, isSignal: true }, { propertyName: "headerTemplate", first: true, predicate: NgHeaderTemplateDirective, descendants: true, read: TemplateRef, isSignal: true }, { propertyName: "footerTemplate", first: true, predicate: NgFooterTemplateDirective, descendants: true, read: TemplateRef, isSignal: true }, { propertyName: "notFoundTemplate", first: true, predicate: NgNotFoundTemplateDirective, descendants: true, read: TemplateRef, isSignal: true }, { propertyName: "placeholderTemplate", first: true, predicate: NgPlaceholderTemplateDirective, descendants: true, read: TemplateRef, isSignal: true }, { propertyName: "typeToSearchTemplate", first: true, predicate: NgTypeToSearchTemplateDirective, descendants: true, read: TemplateRef, isSignal: true }, { propertyName: "loadingTextTemplate", first: true, predicate: NgLoadingTextTemplateDirective, descendants: true, read: TemplateRef, isSignal: true }, { propertyName: "tagTemplate", first: true, predicate: NgTagTemplateDirective, descendants: true, read: TemplateRef, isSignal: true }, { propertyName: "loadingSpinnerTemplate", first: true, predicate: NgLoadingSpinnerTemplateDirective, descendants: true, read: TemplateRef, isSignal: true }, { propertyName: "clearButtonTemplate", first: true, predicate: NgClearButtonTemplateDirective, descendants: true, read: TemplateRef, isSignal: true }, { propertyName: "ngOptions", predicate: NgOptionComponent, descendants: true, isSignal: true }], viewQueries: [{ propertyName: "dropdownPanel", first: true, predicate: i0.forwardRef(() => NgDropdownPanelComponent), descendants: true, isSignal: true }, { propertyName: "searchInput", first: true, predicate: ["searchInput"], descendants: true, isSignal: true }, { propertyName: "clearButton", first: true, predicate: ["clearButton"], descendants: true, isSignal: true }, { propertyName: "_dropdownTemplate", first: true, predicate: ["dropdownTemplate"], descendants: true, isSignal: true }, { propertyName: "_dropdownOutlet", first: true, predicate: ["dropdownTemplate"], descendants: true, read: ViewContainerRef, isSignal: true }, { propertyName: "_selectContainer", first: true, predicate: ["selectContainer"], descendants: true, isSignal: true }], exportAs: ["ngSelect"], usesOnChanges: true, ngImport: i0, template: "<div\n\t#selectContainer\n\t(mousedown)=\"handleMousedown($event)\"\n\t[class.ng-appearance-outline]=\"appearance() === 'outline'\"\n\t[class.ng-appearance-fill]=\"appearance() === 'fill'\"\n\t[class.ng-has-value]=\"hasValue\"\n\tclass=\"ng-select-container\">\n\t<div class=\"ng-value-container\">\n\t\t@if ((selectedItems.length === 0 && !searchTerm) || (fixedPlaceholder() ?? config.fixedPlaceholder)) {\n\t\t\t<ng-template #defaultPlaceholderTemplate>\n\t\t\t\t<div class=\"ng-placeholder\">{{ placeholder() ?? config.placeholder }}</div>\n\t\t\t</ng-template>\n\t\t\t<ng-template [ngTemplateOutlet]=\"placeholderTemplate() || defaultPlaceholderTemplate\"> </ng-template>\n\t\t}\n\n\t\t@if ((!multiLabelTemplate() || !multiple()) && selectedItems.length > 0) {\n\t\t\t@for (item of selectedItems; track trackByOption($index, item)) {\n\t\t\t\t<div [class.ng-value-disabled]=\"item.disabled\" class=\"ng-value\">\n\t\t\t\t\t<ng-template #defaultLabelTemplate>\n\t\t\t\t\t\t<span\n\t\t\t\t\t\t\tclass=\"ng-value-icon left\"\n\t\t\t\t\t\t\trole=\"button\"\n\t\t\t\t\t\t\ttabindex=\"0\"\n\t\t\t\t\t\t\t[attr.aria-label]=\"(removeText() ?? config.removeText) + ' ' + item.label\"\n\t\t\t\t\t\t\t(click)=\"unselect(item)\"\n\t\t\t\t\t\t\t(keydown)=\"handleRemoveKeydown($event, item)\"\n\t\t\t\t\t\t\t>\u00D7</span\n\t\t\t\t\t\t>\n\t\t\t\t\t\t<span class=\"ng-value-label\">{{ item.label }}</span>\n\t\t\t\t\t</ng-template>\n\t\t\t\t\t<ng-template\n\t\t\t\t\t\t[ngTemplateOutlet]=\"labelTemplate() || defaultLabelTemplate\"\n\t\t\t\t\t\t[ngTemplateOutletContext]=\"{ item: item.value, clear: clearItem, label: item.label }\">\n\t\t\t\t\t</ng-template>\n\t\t\t\t</div>\n\t\t\t}\n\t\t}\n\n\t\t@if (multiple() && multiLabelTemplate() && selectedValues.length > 0) {\n\t\t\t<ng-template [ngTemplateOutlet]=\"multiLabelTemplate()\" [ngTemplateOutletContext]=\"{ items: selectedValues, clear: clearItem }\"> </ng-template>\n\t\t}\n\n\t\t<div class=\"ng-input\">\n\t\t\t<input\n\t\t\t\t#searchInput\n\t\t\t\t(blur)=\"onInputBlur($event)\"\n\t\t\t\t(change)=\"$event.stopPropagation()\"\n\t\t\t\t(compositionend)=\"onCompositionEnd(searchInput.value)\"\n\t\t\t\t(compositionstart)=\"onCompositionStart()\"\n\t\t\t\t(focus)=\"onInputFocus($event)\"\n\t\t\t\t(input)=\"filter(searchInput.value)\"\n\t\t\t\t[attr.aria-activedescendant]=\"isOpen() ? itemsList?.markedItem?.htmlId : null\"\n\t\t\t\t[attr.aria-controls]=\"isOpen() ? dropdownId : null\"\n\t\t\t\t[attr.aria-expanded]=\"isOpen()\"\n\t\t\t\t[attr.aria-label]=\"ariaLabel()\"\n\t\t\t\t[attr.id]=\"labelForId()\"\n\t\t\t\t[attr.tabindex]=\"tabIndex()\"\n\t\t\t\t[disabled]=\"disabled()\"\n\t\t\t\t[readOnly]=\"!searchable() || itemsList.maxItemsSelected\"\n\t\t\t\t[value]=\"searchTerm ?? ''\"\n\t\t\t\taria-autocomplete=\"list\"\n\t\t\t\trole=\"combobox\" />\n\t\t</div>\n\t</div>\n\n\t@if (loading()) {\n\t\t<ng-template #defaultLoadingSpinnerTemplate>\n\t\t\t<div class=\"ng-spinner-loader\"></div>\n\t\t</ng-template>\n\t\t<ng-template [ngTemplateOutlet]=\"loadingSpinnerTemplate() || defaultLoadingSpinnerTemplate\"></ng-template>\n\t}\n\n\t@if (showClear()) {\n\t\t<span\n\t\t\tclass=\"ng-clear-wrapper\"\n\t\t\trole=\"button\"\n\t\t\ttabindex=\"0\"\n\t\t\t[attr.tabindex]=\"tabFocusOnClear() ? 0 : -1\"\n\t\t\t[attr.aria-label]=\"clearAllText() || config.clearAllText\"\n\t\t\ttitle=\"{{ clearAllText() || config.clearAllText }}\"\n\t\t\t#clearButton\n\t\t\t(click)=\"handleClearClick($event)\">\n\t\t\t@if (clearButtonTemplate()) {\n\t\t\t\t<ng-container [ngTemplateOutlet]=\"clearButtonTemplate()\"></ng-container>\n\t\t\t} @else {\n\t\t\t\t<span class=\"ng-clear\" aria-hidden=\"true\">\u00D7</span>\n\t\t\t}\n\t\t</span>\n\t}\n\n\t<span class=\"ng-arrow-wrapper\">\n\t\t<span class=\"ng-arrow\"></span>\n\t</span>\n\n\t@if (appearance() === 'outline') {\n\t\t<div class=\"ng-notched-outline\" aria-hidden=\"true\">\n\t\t\t<div class=\"ng-notched-outline-leading\"></div>\n\t\t\t<div class=\"ng-notched-outline-notch\" [style.width.px]=\"outlineNotchWidth()\"></div>\n\t\t\t<div class=\"ng-notched-outline-trailing\"></div>\n\t\t</div>\n\t}\n</div>\n\n<!-- Rendered through a TemplatePortal into the CDK overlay while the select is open;\n the embedded view stays part of this component's view tree, only the DOM moves. -->\n<ng-template #dropdownTemplate>\n\t<ng-dropdown-panel\n\t\tclass=\"ng-dropdown-panel\"\n\t\t[virtualScroll]=\"dropdownVirtualScroll()\"\n\t\t[bufferAmount]=\"bufferAmount()\"\n\t\t[position]=\"dropdownPosition()\"\n\t\t[outsideClickEvent]=\"outsideClickEvent()\"\n\t\t[headerTemplate]=\"headerTemplate()\"\n\t\t[footerTemplate]=\"footerTemplate()\"\n\t\t[filterValue]=\"searchTerm\"\n\t\t[items]=\"itemsList.filteredItems\"\n\t\t[showAddTag]=\"showAddTag\"\n\t\t[markedItem]=\"itemsList.markedItem\"\n\t\t(update)=\"viewPortItems = $event\"\n\t\t(scroll)=\"scroll.emit($event)\"\n\t\t(scrollToEnd)=\"scrollToEnd.emit($event)\"\n\t\t(outsideClick)=\"close()\"\n\t\t[class.ng-select-multiple]=\"multiple()\"\n\t\t[class]=\"panelClasses()\"\n\t\t[listboxId]=\"dropdownId\"\n\t\t[ariaLabelDropdown]=\"ariaLabelDropdown() ?? config.ariaLabelDropdown\"\n\t\t[overlayRef]=\"dropdownOverlayRef\"\n\t\t[selectElement]=\"element\">\n\t\t<ng-container>\n\t\t\t@for (item of viewPortItems; track trackByOption($index, item)) {\n\t\t\t\t<!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus, @angular-eslint/template/mouse-events-have-key-events -->\n\t\t\t\t<div\n\t\t\t\t\t[attr.role]=\"item.children ? 'group' : 'option'\"\n\t\t\t\t\t(click)=\"toggleItem(item)\"\n\t\t\t\t\t(mouseover)=\"onItemHover(item)\"\n\t\t\t\t\t[ngClass]=\"item.classes\"\n\t\t\t\t\t[class.ng-option-disabled]=\"item.disabled\"\n\t\t\t\t\t[class.ng-option-selected]=\"item.selected\"\n\t\t\t\t\t[class.ng-optgroup]=\"item.children\"\n\t\t\t\t\t[class.ng-option]=\"!item.children\"\n\t\t\t\t\t[class.ng-option-child]=\"!!item.parent\"\n\t\t\t\t\t[class.ng-option-marked]=\"item === itemsList.markedItem\"\n\t\t\t\t\t[attr.aria-selected]=\"item.selected\"\n\t\t\t\t\t[attr.id]=\"item?.htmlId\"\n\t\t\t\t\t[attr.aria-setsize]=\"itemsList.filteredItems.length\"\n\t\t\t\t\t[attr.aria-posinset]=\"itemsList.filteredItems.indexOf(item) + 1 || null\">\n\t\t\t\t\t<ng-template #defaultOptionTemplate>\n\t\t\t\t\t\t<span class=\"ng-option-label\">{{ item.label }}</span>\n\t\t\t\t\t</ng-template>\n\t\t\t\t\t<ng-template\n\t\t\t\t\t\t[ngTemplateOutlet]=\"item.children ? optgroupTemplate() || defaultOptionTemplate : optionTemplate() || defaultOptionTemplate\"\n\t\t\t\t\t\t[ngTemplateOutletContext]=\"{ item: item.value, item$: item, index: item.index, searchTerm: searchTerm }\">\n\t\t\t\t\t</ng-template>\n\t\t\t\t</div>\n\t\t\t}\n\t\t\t@if (showAddTag) {\n\t\t\t\t<!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus, @angular-eslint/template/mouse-events-have-key-events -->\n\t\t\t\t<div\n\t\t\t\t\tclass=\"ng-option\"\n\t\t\t\t\t[class.ng-option-marked]=\"!itemsList.markedItem\"\n\t\t\t\t\t(mouseover)=\"itemsList.unmarkItem()\"\n\t\t\t\t\trole=\"option\"\n\t\t\t\t\t[attr.aria-selected]=\"false\"\n\t\t\t\t\t(click)=\"selectTag()\">\n\t\t\t\t\t<ng-template #defaultTagTemplate>\n\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t><span class=\"ng-tag-label\">{{ addTagText() || config.addTagText }}</span\n\t\t\t\t\t\t\t>\"{{ searchTerm }}\"</span\n\t\t\t\t\t\t>\n\t\t\t\t\t</ng-template>\n\t\t\t\t\t<ng-template [ngTemplateOutlet]=\"tagTemplate() || defaultTagTemplate\" [ngTemplateOutletContext]=\"{ searchTerm: searchTerm }\"> </ng-template>\n\t\t\t\t</div>\n\t\t\t}\n\t\t</ng-container>\n\t\t@if (showNoItemsFound()) {\n\t\t\t<ng-template #defaultNotFoundTemplate>\n\t\t\t\t<div class=\"ng-option ng-option-disabled\">{{ notFoundText() ?? config.notFoundText }}</div>\n\t\t\t</ng-template>\n\t\t\t<ng-template [ngTemplateOutlet]=\"notFoundTemplate() || defaultNotFoundTemplate\" [ngTemplateOutletContext]=\"{ searchTerm: searchTerm }\">\n\t\t\t</ng-template>\n\t\t}\n\t\t@if (showTypeToSearch()) {\n\t\t\t<ng-template #defaultTypeToSearchTemplate>\n\t\t\t\t<div class=\"ng-option ng-option-disabled\">{{ typeToSearchText() || config.typeToSearchText }}</div>\n\t\t\t</ng-template>\n\t\t\t<ng-template [ngTemplateOutlet]=\"typeToSearchTemplate() || defaultTypeToSearchTemplate\"></ng-template>\n\t\t}\n\t\t@if (loading() && itemsList.filteredItems.length === 0) {\n\t\t\t<ng-template #defaultLoadingTextTemplate>\n\t\t\t\t<div class=\"ng-option ng-option-disabled\">{{ loadingText() || config.loadingText }}</div>\n\t\t\t</ng-template>\n\t\t\t<ng-template [ngTemplateOutlet]=\"loadingTextTemplate() || defaultLoadingTextTemplate\" [ngTemplateOutletContext]=\"{ searchTerm: searchTerm }\">\n\t\t\t</ng-template>\n\t\t}\n\t</ng-dropdown-panel>\n</ng-template>\n\n<!-- Always present aria-live region -->\n<div aria-atomic=\"true\" aria-live=\"polite\" class=\"ng-visually-hidden\" role=\"status\">\n\t@if (isOpen()) {\n\t\t@if (showNoItemsFound()) {\n\t\t\t{{ notFoundText() ?? config.notFoundText }}\n\t\t} @else if (showTypeToSearch()) {\n\t\t\t{{ typeToSearchText() || config.typeToSearchText }}\n\t\t} @else if (loading() && itemsList.filteredItems.length === 0) {\n\t\t\t{{ loadingText() || config.loadingText }}\n\t\t} @else if (itemsList.markedItem) {\n\t\t\t{{ itemsList.markedItem.label }}\n\t\t}\n\t}\n</div>\n", styles: ["@charset \"UTF-8\";.ng-select{position:relative;display:block;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.ng-select div,.ng-select input,.ng-select span{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.ng-select [hidden]{display:none}.ng-select.ng-select-searchable .ng-select-container .ng-value-container .ng-input{opacity:1}.ng-select:not(.ng-select-searchable) .ng-select-container .ng-value-container .ng-input{pointer-events:none}.ng-select.ng-select-opened .ng-select-container{z-index:1001}.ng-select.ng-select-disabled .ng-select-container .ng-value-container .ng-placeholder,.ng-select.ng-select-disabled .ng-select-container .ng-value-container .ng-value{-webkit-user-select:none;user-select:none;cursor:default}.ng-select.ng-select-disabled .ng-arrow-wrapper{cursor:default}.ng-select.ng-select-filtered .ng-placeholder{display:none}.ng-select .ng-select-container{cursor:default;display:flex;outline:none;overflow:hidden;position:relative;width:100%}.ng-select .ng-select-container .ng-value-container{display:flex;flex:1}.ng-select .ng-select-container .ng-value-container .ng-input{opacity:0}.ng-select .ng-select-container .ng-value-container .ng-input>input{box-sizing:content-box;background:none transparent;border:0 none;box-shadow:none;outline:none;padding:0;cursor:default;width:100%}.ng-select .ng-select-container .ng-value-container .ng-input>input::-ms-clear{display:none}.ng-select .ng-select-container .ng-value-container .ng-input>input[readonly]{-webkit-user-select:unset;user-select:unset;width:0;padding:0}.ng-select.ng-select-single.ng-select-filtered .ng-select-container .ng-value-container .ng-value{visibility:hidden}.ng-select.ng-select-single .ng-select-container .ng-value-container,.ng-select.ng-select-single .ng-select-container .ng-value-container .ng-value{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.ng-select.ng-select-single .ng-select-container .ng-value-container .ng-value .ng-value-icon{display:none}.ng-select.ng-select-single .ng-select-container .ng-value-container .ng-input{position:absolute;left:0;width:100%}.ng-select.ng-select-multiple.ng-select-disabled>.ng-select-container .ng-value-container .ng-value .ng-value-icon{display:none}.ng-select.ng-select-multiple .ng-select-container .ng-value-container{flex-wrap:wrap}.ng-select.ng-select-multiple .ng-select-container .ng-value-container .ng-placeholder{position:absolute}.ng-select.ng-select-multiple .ng-select-container .ng-value-container .ng-value{white-space:nowrap}.ng-select.ng-select-multiple .ng-select-container .ng-value-container .ng-value.ng-value-disabled .ng-value-icon{display:none}.ng-select.ng-select-multiple .ng-select-container .ng-value-container .ng-value .ng-value-icon{cursor:pointer}.ng-select.ng-select-multiple .ng-select-container .ng-value-container .ng-input{flex:1;z-index:2}.ng-select.ng-select-multiple .ng-select-container .ng-value-container .ng-placeholder{z-index:1}.ng-select .ng-clear-wrapper{cursor:pointer;position:relative;width:17px;-webkit-user-select:none;user-select:none}.ng-select .ng-clear-wrapper .ng-clear{display:inline-block;font-size:18px;line-height:1;pointer-events:none}.ng-select .ng-spinner-loader{border-radius:50%;width:17px;height:17px;margin-right:5px;font-size:10px;position:relative;text-indent:-9999em;border-top:2px solid rgba(66,66,66,.2);border-right:2px solid rgba(66,66,66,.2);border-bottom:2px solid rgba(66,66,66,.2);border-left:2px solid #424242;transform:translateZ(0);animation:load8 .8s infinite linear}.ng-select .ng-spinner-loader:after{border-radius:50%;width:17px;height:17px}@-webkit-keyframes load8{0%{-webkit-transform:rotate(0deg);transform:rotate(0)}to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes load8{0%{-webkit-transform:rotate(0deg);transform:rotate(0)}to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}.ng-select .ng-arrow-wrapper{cursor:pointer;position:relative;text-align:center;-webkit-user-select:none;user-select:none}.ng-select .ng-arrow-wrapper .ng-arrow{pointer-events:none;display:inline-block;height:0;width:0;position:relative}.ng-dropdown-panel{box-sizing:border-box;position:relative;opacity:0;width:100%;-webkit-overflow-scrolling:touch}.ng-dropdown-panel .ng-dropdown-panel-items{display:block;height:auto;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;max-height:240px;overflow-y:auto}.ng-dropdown-panel .ng-dropdown-panel-items .ng-optgroup{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.ng-dropdown-panel .ng-dropdown-panel-items .ng-option{box-sizing:border-box;cursor:pointer;display:block;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.ng-dropdown-panel .ng-dropdown-panel-items .ng-option .ng-option-label:empty:before{content:\"\\200b\"}.ng-dropdown-panel .ng-dropdown-panel-items .ng-option .highlighted{font-weight:700;text-decoration:underline}.ng-dropdown-panel .ng-dropdown-panel-items .ng-option.disabled{cursor:default}.ng-dropdown-panel .scroll-host{overflow:hidden;overflow-y:auto;position:relative;display:block;-webkit-overflow-scrolling:touch}.ng-dropdown-panel .scrollable-content{top:0;left:0;width:100%;height:100%;position:absolute}.ng-dropdown-panel .total-padding{width:1px;opacity:0}.cdk-overlay-pane .ng-dropdown-panel{position:relative;inset:auto}.ng-visually-hidden{position:absolute!important;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);border:0;white-space:nowrap}\n"], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: NgDropdownPanelComponent, selector: "ng-dropdown-panel", inputs: ["items", "showAddTag", "markedItem", "position", "bufferAmount", "virtualScroll", "headerTemplate", "footerTemplate", "filterValue", "ariaLabelDropdown", "listboxId", "outsideClickEvent", "popover", "overlayRef", "selectElement"], outputs: ["update", "scroll", "scrollToEnd", "outsideClick"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: NgSelectComponent, decorators: [{
type: Component,
args: [{ selector: 'ng-select', exportAs: 'ngSelect', providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => NgSelectComponent),
multi: true,
},
NgDropdownPanelService,
], encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, imports: [NgClass, NgTemplateOutlet, NgDropdownPanelComponent], host: {
'[class.ng-select]': 'true',
'[class.ng-select-single]': '!multiple()',
'[class.ng-select-typeahead]': 'typeahead()',
'[class.ng-select-multiple]': 'multiple()',
'[class.ng-select-taggable]': 'addTag()',
'[class.ng-select-searchable]': 'searchable()',
'[class.ng-select-clearable]': 'clearable()',
'[class.ng-select-opened]': 'isOpen()',
'[class.ng-select-filtered]': 'filtered',
'[class.ng-select-disabled]': 'disabled()',
}, template: "<div\n\t#selectContainer\n\t(mousedown)=\"handleMousedown($event)\"\n\t[class.ng-appearance-outline]=\"appearance() === 'outline'\"\n\t[class.ng-appearance-fill]=\"appearance() === 'fill'\"\n\t[class.ng-has-value]=\"hasValue\"\n\tclass=\"ng-select-container\">\n\t<div class=\"ng-value-container\">\n\t\t@if ((selectedItems.length === 0 && !searchTerm) || (fixedPlaceholder() ?? config.fixedPlaceholder)) {\n\t\t\t<ng-template #defaultPlaceholderTemplate>\n\t\t\t\t<div class=\"ng-placeholder\">{{ placeholder() ?? config.placeholder }}</div>\n\t\t\t</ng-template>\n\t\t\t<ng-template [ngTemplateOutlet]=\"placeholderTemplate() || defaultPlaceholderTemplate\"> </ng-template>\n\t\t}\n\n\t\t@if ((!multiLabelTemplate() || !multiple()) && selectedItems.length > 0) {\n\t\t\t@for (item of selectedItems; track trackByOption($index, item)) {\n\t\t\t\t<div [class.ng-value-disabled]=\"item.disabled\" class=\"ng-value\">\n\t\t\t\t\t<ng-template #defaultLabelTemplate>\n\t\t\t\t\t\t<span\n\t\t\t\t\t\t\tclass=\"ng-value-icon left\"\n\t\t\t\t\t\t\trole=\"button\"\n\t\t\t\t\t\t\ttabindex=\"0\"\n\t\t\t\t\t\t\t[attr.aria-label]=\"(removeText() ?? config.removeText) + ' ' + item.label\"\n\t\t\t\t\t\t\t(click)=\"unselect(item)\"\n\t\t\t\t\t\t\t(keydown)=\"handleRemoveKeydown($event, item)\"\n\t\t\t\t\t\t\t>\u00D7</span\n\t\t\t\t\t\t>\n\t\t\t\t\t\t<span class=\"ng-value-label\">{{ item.label }}</span>\n\t\t\t\t\t</ng-template>\n\t\t\t\t\t<ng-template\n\t\t\t\t\t\t[ngTemplateOutlet]=\"labelTemplate() || defaultLabelTemplate\"\n\t\t\t\t\t\t[ngTemplateOutletContext]=\"{ item: item.value, clear: clearItem, label: item.label }\">\n\t\t\t\t\t</ng-template>\n\t\t\t\t</div>\n\t\t\t}\n\t\t}\n\n\t\t@if (multiple() && multiLabelTemplate() && selectedValues.length > 0) {\n\t\t\t<ng-template [ngTemplateOutlet]=\"multiLabelTemplate()\" [ngTemplateOutletContext]=\"{ items: selectedValues, clear: clearItem }\"> </ng-template>\n\t\t}\n\n\t\t<div class=\"ng-input\">\n\t\t\t<input\n\t\t\t\t#searchInput\n\t\t\t\t(blur)=\"onInputBlur($event)\"\n\t\t\t\t(change)=\"$event.stopPropagation()\"\n\t\t\t\t(compositionend)=\"onCompositionEnd(searchInput.value)\"\n\t\t\t\t(compositionstart)=\"onCompositionStart()\"\n\t\t\t\t(focus)=\"onInputFocus($event)\"\n\t\t\t\t(input)=\"filter(searchInput.value)\"\n\t\t\t\t[attr.aria-activedescendant]=\"isOpen() ? itemsList?.markedItem?.htmlId : null\"\n\t\t\t\t[attr.aria-controls]=\"isOpen() ? dropdownId : null\"\n\t\t\t\t[attr.aria-expanded]=\"isOpen()\"\n\t\t\t\t[attr.aria-label]=\"ariaLabel()\"\n\t\t\t\t[attr.id]=\"labelForId()\"\n\t\t\t\t[attr.tabindex]=\"tabIndex()\"\n\t\t\t\t[disabled]=\"disabled()\"\n\t\t\t\t[readOnly]=\"!searchable() || itemsList.maxItemsSelected\"\n\t\t\t\t[value]=\"searchTerm ?? ''\"\n\t\t\t\taria-autocomplete=\"list\"\n\t\t\t\trole=\"combobox\" />\n\t\t</div>\n\t</div>\n\n\t@if (loading()) {\n\t\t<ng-template #defaultLoadingSpinnerTemplate>\n\t\t\t<div class=\"ng-spinner-loader\"></div>\n\t\t</ng-template>\n\t\t<ng-template [ngTemplateOutlet]=\"loadingSpinnerTemplate() || defaultLoadingSpinnerTemplate\"></ng-template>\n\t}\n\n\t@if (showClear()) {\n\t\t<span\n\t\t\tclass=\"ng-clear-wrapper\"\n\t\t\trole=\"button\"\n\t\t\ttabindex=\"0\"\n\t\t\t[attr.tabindex]=\"tabFocusOnClear() ? 0 : -1\"\n\t\t\t[attr.aria-label]=\"clearAllText() || config.clearAllText\"\n\t\t\ttitle=\"{{ clearAllText() || config.clearAllText }}\"\n\t\t\t#clearButton\n\t\t\t(click)=\"handleClearClick($event)\">\n\t\t\t@if (clearButtonTemplate()) {\n\t\t\t\t<ng-container [ngTemplateOutlet]=\"clearButtonTemplate()\"></ng-container>\n\t\t\t} @else {\n\t\t\t\t<span class=\"ng-clear\" aria-hidden=\"true\">\u00D7</span>\n\t\t\t}\n\t\t</span>\n\t}\n\n\t<span class=\"ng-arrow-wrapper\">\n\t\t<span class=\"ng-arrow\"></span>\n\t</span>\n\n\t@if (appearance() === 'outline') {\n\t\t<div class=\"ng-notched-outline\" aria-hidden=\"true\">\n\t\t\t<div class=\"ng-notched-outline-leading\"></div>\n\t\t\t<div class=\"ng-notched-outline-notch\" [style.width.px]=\"outlineNotchWidth()\"></div>\n\t\t\t<div class=\"ng-notched-outline-trailing\"></div>\n\t\t</div>\n\t}\n</div>\n\n<!-- Rendered through a TemplatePortal into the CDK overlay while the select is open;\n the embedded view stays part of this component's view tree, only the DOM moves. -->\n<ng-template #dropdownTemplate>\n\t<ng-dropdown-panel\n\t\tclass=\"ng-dropdown-panel\"\n\t\t[virtualScroll]=\"dropdownVirtualScroll()\"\n\t\t[bufferAmount]=\"bufferAmount()\"\n\t\t[position]=\"dropdownPosition()\"\n\t\t[outsideClickEvent]=\"outsideClickEvent()\"\n\t\t[headerTemplate]=\"headerTemplate()\"\n\t\t[footerTemplate]=\"footerTemplate()\"\n\t\t[filterValue]=\"searchTerm\"\n\t\t[items]=\"itemsList.filteredItems\"\n\t\t[showAddTag]=\"showAddTag\"\n\t\t[markedItem]=\"itemsList.markedItem\"\n\t\t(update)=\"viewPortItems = $event\"\n\t\t(scroll)=\"scroll.emit($event)\"\n\t\t(scrollToEnd)=\"scrollToEnd.emit($event)\"\n\t\t(outsideClick)=\"close()\"\n\t\t[class.ng-select-multiple]=\"multiple()\"\n\t\t[class]=\"panelClasses()\"\n\t\t[listboxId]=\"dropdownId\"\n\t\t[ariaLabelDropdown]=\"ariaLabelDropdown() ?? config.ariaLabelDropdown\"\n\t\t[overlayRef]=\"dropdownOverlayRef\"\n\t\t[selectElement]=\"element\">\n\t\t<ng-container>\n\t\t\t@for (item of viewPortItems; track trackByOption($index, item)) {\n\t\t\t\t<!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus, @angular-eslint/template/mouse-events-have-key-events -->\n\t\t\t\t<div\n\t\t\t\t\t[attr.role]=\"item.children ? 'group' : 'option'\"\n\t\t\t\t\t(click)=\"toggleItem(item)\"\n\t\t\t\t\t(mouseover)=\"onItemHover(item)\"\n\t\t\t\t\t[ngClass]=\"item.classes\"\n\t\t\t\t\t[class.ng-option-disabled]=\"item.disabled\"\n\t\t\t\t\t[class.ng-option-selected]=\"item.selected\"\n\t\t\t\t\t[class.ng-optgroup]=\"item.children\"\n\t\t\t\t\t[class.ng-option]=\"!item.children\"\n\t\t\t\t\t[class.ng-option-child]=\"!!item.parent\"\n\t\t\t\t\t[class.ng-option-marked]=\"item === itemsList.markedItem\"\n\t\t\t\t\t[attr.aria-selected]=\"item.selected\"\n\t\t\t\t\t[attr.id]=\"item?.htmlId\"\n\t\t\t\t\t[attr.aria-setsize]=\"itemsList.filteredItems.length\"\n\t\t\t\t\t[attr.aria-posinset]=\"itemsList.filteredItems.indexOf(item) + 1 || null\">\n\t\t\t\t\t<ng-template #defaultOptionTemplate>\n\t\t\t\t\t\t<span class=\"ng-option-label\">{{ item.label }}</span>\n\t\t\t\t\t</ng-template>\n\t\t\t\t\t<ng-template\n\t\t\t\t\t\t[ngTemplateOutlet]=\"item.children ? optgroupTemplate() || defaultOptionTemplate : optionTemplate() || defaultOptionTemplate\"\n\t\t\t\t\t\t[ngTemplateOutletContext]=\"{ item: item.value, item$: item, index: item.index, searchTerm: searchTerm }\">\n\t\t\t\t\t</ng-template>\n\t\t\t\t</div>\n\t\t\t}\n\t\t\t@if (showAddTag) {\n\t\t\t\t<!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus, @angular-eslint/template/mouse-events-have-key-events -->\n\t\t\t\t<div\n\t\t\t\t\tclass=\"ng-option\"\n\t\t\t\t\t[class.ng-option-marked]=\"!itemsList.markedItem\"\n\t\t\t\t\t(mouseover)=\"itemsList.unmarkItem()\"\n\t\t\t\t\trole=\"option\"\n\t\t\t\t\t[attr.aria-selected]=\"false\"\n\t\t\t\t\t(click)=\"selectTag()\">\n\t\t\t\t\t<ng-template #defaultTagTemplate>\n\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t><span class=\"ng-tag-label\">{{ addTagText() || config.addTagText }}</span\n\t\t\t\t\t\t\t>\"{{ searchTerm }}\"</span\n\t\t\t\t\t\t>\n\t\t\t\t\t</ng-template>\n\t\t\t\t\t<ng-template [ngTemplateOutlet]=\"tagTemplate() || defaultTagTemplate\" [ngTemplateOutletContext]=\"{ searchTerm: searchTerm }\"> </ng-template>\n\t\t\t\t</div>\n\t\t\t}\n\t\t</ng-container>\n\t\t@if (showNoItemsFound()) {\n\t\t\t<ng-template #defaultNotFoundTemplate>\n\t\t\t\t<div class=\"ng-option ng-option-disabled\">{{ notFoundText() ?? config.notFoundText }}</div>\n\t\t\t</ng-template>\n\t\t\t<ng-template [ngTemplateOutlet]=\"notFoundTemplate() || defaultNotFoundTemplate\" [ngTemplateOutletContext]=\"{ searchTerm: searchTerm }\">\n\t\t\t</ng-template>\n\t\t}\n\t\t@if (showTypeToSearch()) {\n\t\t\t<ng-template #defaultTypeToSearchTemplate>\n\t\t\t\t<div class=\"ng-option ng-option-disabled\">{{ typeToSearchText() || config.typeToSearchText }}</div>\n\t\t\t</ng-template>\n\t\t\t<ng-template [ngTemplateOutlet]=\"typeToSearchTemplate() || defaultTypeToSearchTemplate\"></ng-template>\n\t\t}\n\t\t@if (loading() && itemsList.filteredItems.length === 0) {\n\t\t\t<ng-template #defaultLoadingTextTemplate>\n\t\t\t\t<div class=\"ng-option ng-option-disabled\">{{ loadingText() || config.loadingText }}</div>\n\t\t\t</ng-template>\n\t\t\t<ng-template [ngTemplateOutlet]=\"loadingTextTemplate() || defaultLoadingTextTemplate\" [ngTemplateOutletContext]=\"{ searchTerm: searchTerm }\">\n\t\t\t</ng-template>\n\t\t}\n\t</ng-dropdown-panel>\n</ng-template>\n\n<!-- Always present aria-live region -->\n<div aria-atomic=\"true\" aria-live=\"polite\" class=\"ng-visually-hidden\" role=\"status\">\n\t@if (isOpen()) {\n\t\t@if (showNoItemsFound()) {\n\t\t\t{{ notFoundText() ?? config.notFoundText }}\n\t\t} @else if (showTypeToSearch()) {\n\t\t\t{{ typeToSearchText() || config.typeToSearchText }}\n\t\t} @else if (loading() && itemsList.filteredItems.length === 0) {\n\t\t\t{{ loadingText() || config.loadingText }}\n\t\t} @else if (itemsList.markedItem) {\n\t\t\t{{ itemsList.markedItem.label }}\n\t\t}\n\t}\n</div>\n", styles: ["@charset \"UTF-8\";.ng-select{position:relative;display:block;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.ng-select div,.ng-select input,.ng-select span{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.ng-select [hidden]{display:none}.ng-select.ng-select-searchable .ng-select-container .ng-value-container .ng-input{opacity:1}.ng-select:not(.ng-select-searchable) .ng-select-container .ng-value-container .ng-input{pointer-events:none}.ng-select.ng-select-opened .ng-select-container{z-index:1001}.ng-select.ng-select-disabled .ng-select-container .ng-value-container .ng-placeholder,.ng-select.ng-select-disabled .ng-select-container .ng-value-container .ng-value{-webkit-user-select:none;user-select:none;cursor:default}.ng-select.ng-select-disabled .ng-arrow-wrapper{cursor:default}.ng-select.ng-select-filtered .ng-placeholder{display:none}.ng-select .ng-select-container{cursor:default;display:flex;outline:none;overflow:hidden;position:relative;width:100%}.ng-select .ng-select-container .ng-value-container{display:flex;flex:1}.ng-select .ng-select-container .ng-value-container .ng-input{opacity:0}.ng-select .ng-select-container .ng-value-container .ng-input>input{box-sizing:content-box;background:none transparent;border:0 none;box-shadow:none;outline:none;padding:0;cursor:default;width:100%}.ng-select .ng-select-container .ng-value-container .ng-input>input::-ms-clear{display:none}.ng-select .ng-select-container .ng-value-container .ng-input>input[readonly]{-webkit-user-select:unset;user-select:unset;width:0;padding:0}.ng-select.ng-select-single.ng-select-filtered .ng-select-container .ng-value-container .ng-value{visibility:hidden}.ng-select.ng-select-single .ng-select-container .ng-value-container,.ng-select.ng-select-single .ng-select-container .ng-value-container .ng-value{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.ng-select.ng-select-single .ng-select-container .ng-value-container .ng-value .ng-value-icon{display:none}.ng-select.ng-select-single .ng-select-container .ng-value-container .ng-input{position:absolute;left:0;width:100%}.ng-select.ng-select-multiple.ng-select-disabled>.ng-select-container .ng-value-container .ng-value .ng-value-icon{display:none}.ng-select.ng-select-multiple .ng-select-container .ng-value-container{flex-wrap:wrap}.ng-select.ng-select-multiple .ng-select-container .ng-value-container .ng-placeholder{position:absolute}.ng-select.ng-select-multiple .ng-select-container .ng-value-container .ng-value{white-space:nowrap}.ng-select.ng-select-multiple .ng-select-container .ng-value-container .ng-value.ng-value-disabled .ng-value-icon{display:none}.ng-select.ng-select-multiple .ng-select-container .ng-value-container .ng-value .ng-value-icon{cursor:pointer}.ng-select.ng-select-multiple .ng-select-container .ng-value-container .ng-input{flex:1;z-index:2}.ng-select.ng-select-multiple .ng-select-container .ng-value-container .ng-placeholder{z-index:1}.ng-select .ng-clear-wrapper{cursor:pointer;position:relative;width:17px;-webkit-user-select:none;user-select:none}.ng-select .ng-clear-wrapper .ng-clear{display:inline-block;font-size:18px;line-height:1;pointer-events:none}.ng-select .ng-spinner-loader{border-radius:50%;width:17px;height:17px;margin-right:5px;font-size:10px;position:relative;text-indent:-9999em;border-top:2px solid rgba(66,66,66,.2);border-right:2px solid rgba(66,66,66,.2);border-bottom:2px solid rgba(66,66,66,.2);border-left:2px solid #424242;transform:translateZ(0);animation:load8 .8s infinite linear}.ng-select .ng-spinner-loader:after{border-radius:50%;width:17px;height:17px}@-webkit-keyframes load8{0%{-webkit-transform:rotate(0deg);transform:rotate(0)}to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes load8{0%{-webkit-transform:rotate(0deg);transform:rotate(0)}to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}.ng-select .ng-arrow-wrapper{cursor:pointer;position:relative;text-align:center;-webkit-user-select:none;user-select:none}.ng-select .ng-arrow-wrapper .ng-arrow{pointer-events:none;display:inline-block;height:0;width:0;position:relative}.ng-dropdown-panel{box-sizing:border-box;position:relative;opacity:0;width:100%;-webkit-overflow-scrolling:touch}.ng-dropdown-panel .ng-dropdown-panel-items{display:block;height:auto;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;max-height:240px;overflow-y:auto}.ng-dropdown-panel .ng-dropdown-panel-items .ng-optgroup{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.ng-dropdown-panel .ng-dropdown-panel-items .ng-option{box-sizing:border-box;cursor:pointer;display:block;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.ng-dropdown-panel .ng-dropdown-panel-items .ng-option .ng-option-label:empty:before{content:\"\\200b\"}.ng-dropdown-panel .ng-dropdown-panel-items .ng-option .highlighted{font-weight:700;text-decoration:underline}.ng-dropdown-panel .ng-dropdown-panel-items .ng-option.disabled{cursor:default}.ng-dropdown-panel .scroll-host{overflow:hidden;overflow-y:auto;position:relative;display:block;-webkit-overflow-scrolling:touch}.ng-dropdown-panel .scrollable-content{top:0;left:0;width:100%;height:100%;position:absolute}.ng-dropdown-panel .total-padding{width:1px;opacity:0}.cdk-overlay-pane .ng-dropdown-panel{position:relative;inset:auto}.ng-visually-hidden{position:absolute!important;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);border:0;white-space:nowrap}\n"] }]
}], ctorParameters: () => [], propDecorators: { _ariaLabelDropdown: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabelDropdown", required: false }] }], _ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], _markFirst: [{ type: i0.Input, args: [{ isSignal: true, alias: "markFirst", required: false }] }], _placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], _fixedPlaceholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "fixedPlaceholder", required: false }] }], _notFoundText: [{ type: i0.Input, args: [{ isSignal: true, alias: "notFoundText", required: false }] }], _typeToSearchText: [{ type: i0.Input, args: [{ isSignal: true, alias: "typeToSearchText", required: false }] }], _preventToggleOnRightClick: [{ type: i0.Input, args: [{ isSignal: true, alias: "preventToggleOnRightClick", required: false }] }], _addTagText: [{ type: i0.Input, args: [{ isSignal: true, alias: "addTagText", required: false }] }], _loadingText: [{ type: i0.Input, args: [{ isSignal: true, alias: "loadingText", required: false }] }], _clearAllText: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearAllText", required: false }] }], _removeText: [{ type: i0.Input, args: [{ isSignal: true, alias: "removeText", required: false }] }], _dropdownPosition: [{ type: i0.Input, args: [{ isSignal: true, alias: "dropdownPosition", required: false }] }], _appendTo: [{ type: i0.Input, args: [{ isSignal: true, alias: "appendTo", required: false }] }], _outsideClickEvent: [{ type: i0.Input, args: [{ isSignal: true, alias: "outsideClickEvent", required: false }] }], _loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], _closeOnSelect: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeOnSelect", required: false }] }], _hideSelected: [{ type: i0.Input, args: [{ isSignal: true, alias: "hideSelected", required: false }] }], _selectOnTab: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectOnTab", required: false }] }], _openOnEnter: [{ type: i0.Input, args: [{ isSignal: true, alias: "openOnEnter", required: false }] }], _maxSelectedItems: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxSelectedItems", required: false }] }], _groupBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "groupBy", required: false }] }], _groupValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "groupValue", required: false }] }], _bufferAmount: [{ type: i0.Input, args: [{ isSignal: true, alias: "bufferAmount", required: false }] }], _virtualScroll: [{ type: i0.Input, args: [{ isSignal: true, alias: "virtualScroll", required: false }] }], _selectableGroup: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectableGroup", required: false }] }], _tabFocusOnClearButton: [{ type: i0.Input, args: [{ isSignal: true, alias: "tabFocusOnClearButton", required: false }] }], _selectableGroupAsModel: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectableGroupAsModel", required: false }] }], _searchFn: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchFn", required: false }] }], _trackByFn: [{ type: i0.Input, args: [{ isSignal: true, alias: "trackByFn", required: false }] }], _clearOnBackspace: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearOnBackspace", required: false }] }], _labelForId: [{ type: i0.Input, args: [{ isSignal: true, alias: "labelForId", required: false }] }], _inputAttrs: [{ type: i0.Input, args: [{ isSignal: true, alias: "inputAttrs", required: false }] }], _tabIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "tabIndex", required: false }] }], _readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], _searchWhileComposing: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchWhileComposing", required: false }] }], _minTermLength: [{ type: i0.Input, args: [{ isSignal: true, alias: "minTermLength", required: false }] }], _editableSearchTerm: [{ type: i0.Input, args: [{ isSignal: true, alias: "editableSearchTerm", required: false }] }], _ngClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "ngClass", required: false }] }], _panelClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "panelClass", required: false }] }], _typeahead: [{ type: i0.Input, args: [{ isSignal: true, alias: "typeahead", required: false }] }], _multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], _addTag: [{ type: i0.Input, args: [{ isSignal: true, alias: "addTag", required: false }] }], _searchable: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchable", required: false }] }], _clearable: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearable", required: false }] }], _clearKeepsDisabledOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearKeepsDisabledOptions", required: false }] }], _deselectOnClick: [{ type: i0.Input, args: [{ isSignal: true, alias: "deselectOnClick", required: false }] }], _clearSearchOnAdd: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearSearchOnAdd", required: false }] }], _compareWith: [{ type: i0.Input, args: [{ isSignal: true, alias: "compareWith", required: false }] }], _keyDownFn: [{ type: i0.Input, args: [{ isSignal: true, alias: "keyDownFn", required: false }] }], _popover: [{ type: i0.Input, args: [{ isSignal: true, alias: "popover", required: false }] }], bindLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "bindLabel", required: false }] }, { type: i0.Output, args: ["bindLabelChange"] }], bindValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "bindValue", required: false }] }, { type: i0.Output, args: ["bindValueChange"] }], appearance: [{ type: i0.Input, args: [{ isSignal: true, alias: "appearance", required: false }] }, { type: i0.Output, args: ["appearanceChange"] }], isOpen: [{ type: i0.Input, args: [{ isSignal: true, alias: "isOpen", required: false }] }, { type: i0.Output, args: ["isOpenChange"] }], items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: false }] }, { type: i0.Output, args: ["itemsChange"] }], blurEvent: [{ type: i0.Output, args: ["blur"] }], focusEvent: [{ type: i0.Output, args: ["focus"] }], changeEvent: [{ type: i0.Output, args: ["change"] }], openEvent: [{ type: i0.Output, args: ["open"] }], closeEvent: [{ type: i0.Output, args: ["close"] }], searchEvent: [{ type: i0.Output, args: ["search"] }], clearEvent: [{ type: i0.Output, args: ["clear"] }], addEvent: [{ type: i0.Output, args: ["add"] }], removeEvent: [{ type: i0.Output, args: ["remove"] }], scroll: [{ type: i0.Output, args: ["scroll"] }], scrollToEnd: [{ type: i0.Output, args: ["scrollToEnd"] }], optionTemplate: [{ type: i0.ContentChild, args: [i0.forwardRef(() => NgOptionTemplateDirective), { ...{ read: TemplateRef }, isSignal: true }] }], optgroupTemplate: [{ type: i0.ContentChild, args: [i0.forwardRef(() => NgOptgroupTemplateDirective), { ...{ read: TemplateRef }, isSignal: true }] }], labelTemplate: [{ type: i0.ContentChild, args: [i0.forwardRef(() => NgLabelTemplateDirective), { ...{ read: TemplateRef }, isSignal: true }] }], multiLabelTemplate: [{ type: i0.ContentChild, args: [i0.forwardRef(() => NgMultiLabelTemplateDirective), { ...{ read: TemplateRef }, isSignal: true }] }], headerTemplate: [{ type: i0.ContentChild, args: [i0.forwardRef(() => NgHeaderTemplateDirective), { ...{ read: TemplateRef }, isSignal: true }] }], footerTemplate: [{ type: i0.ContentChild, args: [i0.forwardRef(() => NgFooterTemplateDirective), { ...{ read: TemplateRef }, isSignal: true }] }], notFoundTemplate: [{ type: i0.ContentChild, args: [i0.forwardRef(() => NgNotFoundTemplateDirective), { ...{ read: TemplateRef }, isSignal: true }] }], placeholderTemplate: [{ type: i0.ContentChild, args: [i0.forwardRef(() => NgPlaceholderTemplateDirective), { ...{ read: TemplateRef }, isSignal: true }] }], typeToSearchTemplate: [{ type: i0.ContentChild, args: [i0.forwardRef(() => NgTypeToSearchTemplateDirective), { ...{ read: TemplateRef }, isSignal: true }] }], loadingTextTemplate: [{ type: i0.ContentChild, args: [i0.forwardRef(() => NgLoadingTextTemplateDirective), { ...{ read: TemplateRef }, isSignal: true }] }], tagTemplate: [{ type: i0.ContentChild, args: [i0.forwardRef(() => NgTagTemplateDirective), { ...{ read: TemplateRef }, isSignal: true }] }], loadingSpinnerTemplate: [{ type: i0.ContentChild, args: [i0.forwardRef(() => NgLoadingSpinnerTemplateDirective), { ...{ read: TemplateRef }, isSignal: true }] }], clearButtonTemplate: [{ type: i0.ContentChild, args: [i0.forwardRef(() => NgClearButtonTemplateDirective), { ...{ read: TemplateRef }, isSignal: true }] }], ngOptions: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => NgOptionComponent), { ...{ descendants: true }, isSignal: true }] }], dropdownPanel: [{ type: i0.ViewChild, args: [forwardRef(() => NgDropdownPanelComponent), { isSignal: true }] }], searchInput: [{ type: i0.ViewChild, args: ['searchInput', { isSignal: true }] }], clearButton: [{ type: i0.ViewChild, args: ['clearButton', { isSignal: true }] }], _dropdownTemplate: [{ type: i0.ViewChild, args: ['dropdownTemplate', { isSignal: true }] }], _dropdownOutlet: [{ type: i0.ViewChild, args: ['dropdownTemplate', { ...{ read: ViewContainerRef }, isSignal: true }] }], _selectContainer: [{ type: i0.ViewChild, args: ['selectContainer', { isSignal: true }] }], handleKeyDown: [{
type: HostListener,
args: ['keydown', ['$event']]
}] } });
/**
* Provides the compatibility NgModule wrapper for the standalone ng-select declarations.
*
* @since 3.0.0
*/
class NgSelectModule {
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: NgSelectModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "22.1.4", ngImport: i0, type: NgSelectModule, imports: [NgDropdownPanelComponent,
NgOptionComponent,
NgSelectComponent,
NgOptgroupTemplateDirective,
NgOptionTemplateDirective,
NgLabelTemplateDirective,
NgMultiLabelTemplateDirective,
NgHeaderTemplateDirective,
NgFooterTemplateDirective,
NgPlaceholderTemplateDirective,
NgClearButtonTemplateDirective,
NgNotFoundTemplateDirective,
NgTypeToSearchTemplateDirective,
NgLoadingTextTemplateDirective,
NgTagTemplateDirective,
NgLoadingSpinnerTemplateDirective,
NgItemLabelDirective], exports: [NgSelectComponent,
NgOptionComponent,
NgOptgroupTemplateDirective,
NgOptionTemplateDirective,
NgLabelTemplateDirective,
NgMultiLabelTemplateDirective,
NgHeaderTemplateDirective,
NgFooterTemplateDirective,
NgPlaceholderTemplateDirective,
NgNotFoundTemplateDirective,
NgTypeToSearchTemplateDirective,
NgLoadingTextTemplateDirective,
NgTagTemplateDirective,
NgLoadingSpinnerTemplateDirective,
NgClearButtonTemplateDirective] }); }
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: NgSelectModule, providers: provideNgSelect() }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: NgSelectModule, decorators: [{
type: NgModule,
args: [{
imports: [
NgDropdownPanelComponent,
NgOptionComponent,
NgSelectComponent,
NgOptgroupTemplateDirective,
NgOptionTemplateDirective,
NgLabelTemplateDirective,
NgMultiLabelTemplateDirective,
NgHeaderTemplateDirective,
NgFooterTemplateDirective,
NgPlaceholderTemplateDirective,
NgClearButtonTemplateDirective,
NgNotFoundTemplateDirective,
NgTypeToSearchTemplateDirective,
NgLoadingTextTemplateDirective,
NgTagTemplateDirective,
NgLoadingSpinnerTemplateDirective,
NgItemLabelDirective,
],
exports: [
NgSelectComponent,
NgOptionComponent,
NgOptgroupTemplateDirective,
NgOptionTemplateDirective,
NgLabelTemplateDirective,
NgMultiLabelTemplateDirective,
NgHeaderTemplateDirective,
NgFooterTemplateDirective,
NgPlaceholderTemplateDirective,
NgNotFoundTemplateDirective,
NgTypeToSearchTemplateDirective,
NgLoadingTextTemplateDirective,
NgTagTemplateDirective,
NgLoadingSpinnerTemplateDirective,
NgClearButtonTemplateDirective,
],
providers: provideNgSelect(),
}]
}] });
/**
* Provides the default ng-select selection-model dependency.
*
* @since 15.2.0
*/
function provideNgSelect() {
return [
{
provide: SELECTION_MODEL_FACTORY,
useValue: DefaultSelectionModelFactory,
},
];
}
/*
* Public API Surface of ng-select
*/
/**
* Generated bundle index. Do not edit.
*/
export { ConsoleService, DefaultSelectionModel, DefaultSelectionModelFactory, NgClearButtonTemplateDirective, NgDropdownPanelComponent, NgDropdownPanelService, NgFooterTemplateDirective, NgHeaderTemplateDirective, NgItemLabelDirective, NgLabelTemplateDirective, NgLoadingSpinnerTemplateDirective, NgLoadingTextTemplateDirective, NgMultiLabelTemplateDirective, NgNotFoundTemplateDirective, NgOptgroupTemplateDirective, NgOptionComponent, NgOptionTemplateDirective, NgPlaceholderTemplateDirective, NgSelectComponent, NgSelectConfig, NgSelectModule, NgTagTemplateDirective, NgTypeToSearchTemplateDirective, SELECTION_MODEL_FACTORY };
//# sourceMappingURL=ng-select-ng-select.mjs.map