ng-select2-component
Version:
An Angular select2 component.
1 lines • 138 kB
Source Map (JSON)
{"version":3,"file":"ng-select2-component.mjs","sources":["../../../projects/ng-select2-component/src/lib/select2-const.ts","../../../projects/ng-select2-component/src/lib/select2-hint.component.ts","../../../projects/ng-select2-component/src/lib/select2-label.component.ts","../../../projects/ng-select2-component/src/lib/select2-utils.ts","../../../projects/ng-select2-component/src/lib/select2.component.ts","../../../projects/ng-select2-component/src/lib/select2.component.html","../../../projects/ng-select2-component/src/public_api.ts","../../../projects/ng-select2-component/src/ng-select2-component.ts"],"sourcesContent":["export const timeout = 200;\n\nexport const unicodePatterns: { l: string; s: RegExp }[] = [\n { l: 'a', s: /[ⓐaẚàáâầấẫẩãāăằắẵẳȧǡäǟảåǻǎȁȃạậặḁąⱥɐ]/gi },\n { l: 'aa', s: /ꜳ/gi },\n { l: 'ae', s: /[æǽǣ]/gi },\n { l: 'ao', s: /ꜵ/gi },\n { l: 'au', s: /ꜷ/gi },\n { l: 'av', s: /[ꜹꜻ]/gi },\n { l: 'ay', s: /ꜽ/gi },\n { l: 'b', s: /[ⓑbḃḅḇƀƃɓ]/gi },\n { l: 'c', s: /[ⓒcćĉċčçḉƈȼꜿↄ]/gi },\n { l: 'd', s: /[ⓓdḋďḍḑḓḏđƌɖɗꝺ]/gi },\n { l: 'dz', s: /[dzdž]/gi },\n { l: 'e', s: /[ⓔeèéêềếễểẽēḕḗĕėëẻěȅȇẹệȩḝęḙḛɇɛǝ]/gi },\n { l: 'f', s: /[ⓕfḟƒꝼ]/gi },\n { l: 'g', s: /[ⓖgǵĝḡğġǧģǥɠꞡᵹꝿ]/gi },\n { l: 'h', s: /[ⓗhĥḣḧȟḥḩḫẖħⱨⱶɥ]/gi },\n { l: 'hv', s: /ƕ/gi },\n { l: 'i', s: /[ⓘiìíîĩīĭİïḯỉǐȉȋịįḭɨı]/gi },\n { l: 'j', s: /[ⓙjĵǰɉ]/gi },\n { l: 'k', s: /[ⓚkḱǩḳķḵƙⱪꝁꝃꝅꞣ]/gi },\n { l: 'l', s: /[ⓛlŀĺľḷḹļḽḻſłƚɫⱡꝉꞁꝇꝆ]/gi },\n { l: 'lj', s: /lj/gi },\n { l: 'm', s: /[ⓜmḿṁṃɱɯ]/gi },\n { l: 'n', s: /[ⓝnǹńñṅňṇņṋṉƞɲʼnꞑꞥ]/gi },\n { l: 'nj', s: /nj/gi },\n { l: 'o', s: /[ⓞoòóôồốỗổõṍȭṏōṑṓŏȯȱöȫỏőǒȍȏơờớỡởợọộǫǭøǿɔƟꝋꝍɵ]/gi },\n { l: 'oi', s: /ƣ/gi },\n { l: 'oe', s: /œ/gi },\n { l: 'oo', s: /ꝏ/gi },\n { l: 'ou', s: /ȣ/gi },\n { l: 'p', s: /[ⓟpṕṗƥᵽꝑꝓꝕ]/gi },\n { l: 'q', s: /[ⓠqɋꝗꝙ]/gi },\n { l: 'r', s: /[ⓡrŕṙřȑȓṛṝŗṟɍɽꝛꞧꞃ]/gi },\n { l: 's', s: /[ⓢsßẞśṥŝṡšṧṣṩșşȿꞩꞅẛ]/gi },\n { l: 't', s: /[ⓣtṫẗťṭțţṱṯŧƭʈⱦꞇ]/gi },\n { l: 'tz', s: /ꜩ/gi },\n { l: 'u', s: /[ⓤuùúûũṹūṻŭüǜǘǖǚủůűǔȕȗưừứữửựụṳųṷṵʉ]/gi },\n { l: 'v', s: /[ⓥvṽṿʋꝟʌ]/gi },\n { l: 'vy', s: /ꝡ/gi },\n { l: 'w', s: /[ⓦwẁẃŵẇẅẘẉⱳ]/gi },\n { l: 'x', s: /[ⓧxẋẍ]/gi },\n { l: 'y', s: /[ⓨyỳýŷỹȳẏÿỷẙỵƴɏỿ]/gi },\n { l: 'z', s: /[ⓩzźẑżžẓẕƶȥɀⱬꝣ]/gi },\n];\n\nexport const defaultMinCountForSearch = 6;\n\nexport const protectRegexp = new RegExp('[\\\\-\\\\[\\\\]\\\\/\\\\{\\\\}\\\\(\\\\)\\\\*\\\\+\\\\?\\\\.\\\\\\\\\\\\^\\\\$\\\\|]', 'g');\n","import { Directive } from '@angular/core';\n\n@Directive({ selector: 'select2-hint, ng-select2-hint', standalone: true })\nexport class Select2Hint {}\n","import { Directive } from '@angular/core';\n\n@Directive({ selector: 'select2-label, ng-select2-label', standalone: true })\nexport class Select2Label {}\n","import { defaultMinCountForSearch, protectRegexp, unicodePatterns } from './select2-const';\nimport { Select2Data, Select2Group, Select2Option, Select2UpdateValue, Select2Value } from './select2-interfaces';\n\n\nexport class Select2Utils {\n static getOptionByValue(data: Select2Data, value: Select2Value ) {\n if (Array.isArray(data)) {\n for (const groupOrOption of data) {\n const options = (groupOrOption as Select2Group).options;\n if (options) {\n for (const option of options) {\n if (option.value === value) {\n return option;\n }\n }\n } else if ((groupOrOption as Select2Option).value === value) {\n return groupOrOption as Select2Option;\n }\n }\n }\n return null;\n }\n\n static getOptionsByValue(\n data: Select2Data,\n value: Select2UpdateValue | null | undefined,\n multiple: boolean | null | undefined,\n ) {\n if (multiple) {\n const values: Select2Value[] = Array.isArray(value) ? value : [];\n const result: Select2Option[] = [];\n for (const v of values) {\n const option = Select2Utils.getOptionByValue(data, v);\n if (option) {\n result.push(option);\n }\n }\n return result;\n }\n return Select2Utils.getOptionByValue(data, value as Select2Value);\n }\n\n static getFirstAvailableOption(data: Select2Data): Select2Option | null {\n if (Array.isArray(data)) {\n for (const groupOrOption of data) {\n const options = (groupOrOption as Select2Group).options;\n if (options) {\n for (const option of options) {\n if (!option.disabled) {\n return option;\n }\n }\n } else {\n const option = groupOrOption as Select2Option;\n if (!option.disabled) {\n return option;\n }\n }\n }\n }\n return null;\n }\n\n static optionIsNotInFilteredData(filteredData: Select2Data, option: Select2Option | null): boolean {\n if (Select2Utils.isNullOrUndefined(option)) {\n return true;\n }\n for (const groupOrOption of filteredData) {\n const options = (groupOrOption as Select2Group).options;\n if (options && options.includes(option)) {\n return false;\n } else if (groupOrOption === option) {\n return false;\n }\n }\n return true;\n }\n\n static getPreviousOption(filteredData: Select2Data, hoveringOption: Select2Option | null): Select2Option | null {\n let findIt = Select2Utils.isNullOrUndefined(hoveringOption);\n for (let i = filteredData.length - 1; i >= 0; i--) {\n const groupOrOption = filteredData[i];\n const options = (groupOrOption as Select2Group).options;\n if (options) {\n for (let j = options.length - 1; j >= 0; j--) {\n const option = options[j];\n if (findIt && !option.disabled && !option.hide) {\n return option;\n }\n if (!findIt) {\n findIt = option === hoveringOption;\n }\n }\n } else {\n const option = groupOrOption as Select2Option;\n if (findIt && !option.disabled && !option.hide) {\n return option;\n }\n if (!findIt) {\n findIt = option === hoveringOption;\n }\n }\n }\n return null;\n }\n\n static getNextOption(filteredData: Select2Data | null, hoveringOption: Select2Option | null): Select2Option | null {\n let findIt = Select2Utils.isNullOrUndefined(hoveringOption);\n if (filteredData) {\n for (const groupOrOption of filteredData) {\n const options = (groupOrOption as Select2Group).options;\n if (options) {\n for (const option of options) {\n if (findIt) {\n if (!option.disabled && !option.hide) {\n return option;\n }\n } else if (!findIt) {\n findIt = option === hoveringOption;\n }\n }\n } else {\n const option = groupOrOption as Select2Option;\n if (findIt) {\n if (!option.disabled && !option.hide) {\n return option;\n }\n } else if (!findIt) {\n findIt = option === hoveringOption;\n }\n }\n }\n }\n return null;\n }\n\n static getFirstOption(filteredData: Select2Data): Select2Option | null {\n const firstElement = filteredData[0];\n if (this.isOption(firstElement)) {\n return firstElement ?? null;\n } else {\n return firstElement.options[0] ?? null;\n }\n }\n\n static getLastOption(filteredData: Select2Data): Select2Option | null {\n const lastElement = filteredData.at(-1);\n if (!lastElement) {\n return null;\n }\n if (this.isOption(lastElement)) {\n return lastElement;\n } else {\n return lastElement.options.at(-1) ?? null;\n }\n }\n\n static isGroup(element: Select2Group | Select2Option): element is Select2Group {\n return !!(element as Select2Group).options;\n }\n\n static isOption(element: Select2Group | Select2Option): element is Select2Option {\n return !this.isGroup(element);\n }\n\n static getReduceData(data: Select2Data, maxResults = 0): { result: Select2Data; reduce: boolean } {\n if (maxResults > 0) {\n let counter = 0;\n const result: Select2Data = [];\n // debugger;\n\n for (const groupOrOption of data) {\n const options = (groupOrOption as Select2Group).options;\n if (options) {\n const group: Select2Group | Select2Option = {\n ...groupOrOption,\n options: [],\n };\n result.push(group);\n for (const item of options) {\n group.options.push(item);\n counter++;\n if (counter === maxResults) {\n return { result, reduce: true };\n }\n }\n } else {\n result.push(groupOrOption);\n counter++;\n }\n if (counter === maxResults) {\n return { result, reduce: true };\n }\n }\n return { result, reduce: false };\n } else {\n return { result: data, reduce: false };\n }\n }\n\n static getFilteredData(\n data: Select2Data,\n searchText: string | null,\n editPattern?: (str: string) => string,\n ): Select2Data {\n if (searchText) {\n const result: Select2Data = [];\n for (const groupOrOption of data) {\n const options = (groupOrOption as Select2Group).options;\n if (options) {\n if (options.some(group => Select2Utils.containSearchText(group.label, searchText, editPattern))) {\n const filteredOptions = options.filter(group =>\n Select2Utils.containSearchText(group.label, searchText, editPattern),\n );\n result.push({\n ...groupOrOption,\n options: filteredOptions,\n });\n }\n } else if (Select2Utils.containSearchText(groupOrOption.label, searchText, editPattern)) {\n result.push(groupOrOption);\n }\n }\n return result;\n } else {\n return data;\n }\n }\n\n static getFilteredSelectedData(\n data: Select2Data,\n selectedOptions: Select2Option | Select2Option[] | null,\n ): Select2Data {\n const result: Select2Data = [];\n for (const groupOrOption of data) {\n const options = (groupOrOption as Select2Group).options;\n if (options) {\n const filteredOptions = options.filter(\n group => Select2Utils.isSelected(selectedOptions, group, true) === 'false',\n );\n if (filteredOptions.length) {\n result.push({\n ...groupOrOption,\n options: filteredOptions,\n });\n }\n } else if (Select2Utils.isSelected(selectedOptions, groupOrOption as Select2Option, true) === 'false') {\n result.push(groupOrOption);\n }\n }\n return result;\n }\n\n static isSearchboxHidden(data: Select2Data, minCountForSearch?: number): boolean {\n if (minCountForSearch === undefined || minCountForSearch === null || isNaN(+minCountForSearch)) {\n minCountForSearch = defaultMinCountForSearch;\n }\n const optionCount = Select2Utils.getOptionsCount(data);\n return optionCount < +minCountForSearch;\n }\n\n static isSelected(\n options: Select2Option | Select2Option[] | null,\n option: Select2Option,\n multiple: boolean | null | undefined,\n ) {\n return multiple\n ? options && (options as Select2Option[]).some(op => op.value === option.value)\n ? 'true'\n : 'false'\n : options && option.value === (options as Select2Option).value\n ? 'true'\n : 'false';\n }\n\n static removeSelection(options: Select2Option | Select2Option[] | null, option: Select2Option) {\n for (let i = 0; i < (options as Select2Option[]).length; i++) {\n if ((options as Select2Option[])[i].value === option.value) {\n (options as Select2Option[]).splice(i, 1);\n return;\n }\n }\n }\n\n private static getOptionsCount(data: Select2Data) {\n let count = 0;\n if (Array.isArray(data)) {\n for (const groupOrOption of data) {\n const options = (groupOrOption as Select2Group).options;\n count += options ? options.length : 1;\n }\n }\n return count;\n }\n\n private static isNullOrUndefined(value: any): value is null | undefined {\n return value === null || value === undefined;\n }\n\n private static containSearchText(\n label: string,\n searchText: string | null,\n editPattern: ((str: string) => string) | undefined,\n ): boolean {\n return searchText\n ? Select2Utils.formatSansUnicode(label).match(\n new RegExp(Select2Utils.formatPattern(searchText, editPattern), 'i'),\n ) !== null\n : true;\n }\n\n private static protectPattern(str: string): string {\n return str.replace(protectRegexp, '\\\\$&');\n }\n\n private static formatSansUnicode(str: string): string {\n for (const unicodePattern of unicodePatterns) {\n str = str.replace(unicodePattern.s, unicodePattern.l);\n }\n return str;\n }\n\n private static formatPattern(str: string, editPattern: ((str: string) => string) | undefined): string {\n str = Select2Utils.formatSansUnicode(Select2Utils.protectPattern(str));\n\n if (editPattern && typeof editPattern === 'function') {\n str = editPattern(str);\n }\n return str;\n }\n}\n","import { CdkDrag, CdkDragDrop, CdkDropList, moveItemInArray } from '@angular/cdk/drag-drop';\nimport {\n CdkConnectedOverlay,\n CdkOverlayOrigin,\n ConnectedOverlayPositionChange,\n ConnectionPositionPair,\n VerticalConnectionPos,\n} from '@angular/cdk/overlay';\nimport { ViewportRuler } from '@angular/cdk/scrolling';\nimport { NgTemplateOutlet } from '@angular/common';\nimport {\n AfterViewInit,\n Attribute,\n ChangeDetectionStrategy,\n ChangeDetectorRef,\n Component,\n DoCheck,\n ElementRef,\n HostBinding,\n HostListener,\n OnChanges,\n OnDestroy,\n OnInit,\n Optional,\n Self,\n SimpleChanges,\n TemplateRef,\n booleanAttribute,\n computed,\n input,\n numberAttribute,\n output,\n signal,\n viewChild,\n viewChildren,\n} from '@angular/core';\nimport { toObservable } from '@angular/core/rxjs-interop';\nimport { ControlValueAccessor, FormGroupDirective, NgControl, NgForm } from '@angular/forms';\n\nimport { InfiniteScrollDirective } from 'ngx-infinite-scroll';\nimport { Subject, Subscription } from 'rxjs';\n\nimport {\n Select2AutoCreateEvent,\n Select2Data,\n Select2Group,\n Select2Option,\n Select2RemoveEvent,\n Select2ScrollEvent,\n Select2SearchEvent,\n Select2SelectionOverride,\n Select2Template,\n Select2UpdateEvent,\n Select2UpdateValue,\n Select2Value,\n} from './select2-interfaces';\nimport { Select2Utils } from './select2-utils';\n\nlet nextUniqueId = 0;\n\ninterface KeyInfo {\n key: string;\n altKey: boolean;\n}\n\nconst OPEN_KEYS_NATIVE: (string | KeyInfo)[] = ['Enter', ' '];\nconst CLOSE_KEYS_NATIVE: (string | KeyInfo)[] = ['ArrowDown', 'ArrowUp', 'Home', 'End', 'PageUp', 'PageDown'];\nconst OPEN_KEYS: (string | KeyInfo)[] = ['ArrowDown', 'ArrowUp', 'Enter', ' ', 'Home', 'End', 'PageUp', 'PageDown'];\nconst ON_OPEN_KEYS: (string | KeyInfo)[] = ['Home', 'End', 'PageUp', 'PageDown'];\nconst CLOSE_KEYS: (string | KeyInfo)[] = ['Escape', 'Tab', { key: 'ArrowUp', altKey: true }];\n\n@Component({\n selector: 'select2, ng-select2',\n templateUrl: './select2.component.html',\n styleUrls: ['./select2.component.scss'],\n standalone: true,\n imports: [CdkOverlayOrigin, NgTemplateOutlet, CdkConnectedOverlay, InfiniteScrollDirective, CdkDropList, CdkDrag],\n host: {\n '[id]': 'id()',\n '[class.select2-selection-nowrap]': 'selectionNoWrap()',\n },\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class Select2 implements ControlValueAccessor, OnInit, DoCheck, AfterViewInit, OnDestroy, OnChanges {\n readonly _uid = `select2-${nextUniqueId++}`;\n // ----------------------- signal-input\n\n /** data of options & option groups */\n readonly data = input.required<Select2Data>();\n\n /** minimum characters to start filter search */\n readonly minCharForSearch = input(0, { transform: numberAttribute });\n\n /** text placeholder */\n readonly displaySearchStatus = input<'default' | 'hidden' | 'always' | undefined>(undefined);\n\n /** text placeholder */\n readonly placeholder = input<string | undefined>(undefined);\n\n /** in multiple: maximum selection element (0 = no limit) */\n readonly limitSelection = input(0, { transform: numberAttribute });\n\n /** dropdown position */\n readonly listPosition = input<'above' | 'below' | 'auto'>('below');\n\n /** overlay with CDK Angular position */\n readonly overlay = input<boolean, unknown>(false, { transform: booleanAttribute });\n\n /** select one or more item */\n readonly multiple = input<boolean, unknown>(false, { transform: booleanAttribute });\n\n /** drag'n drop list of items in multiple */\n readonly multipleDrag = input<boolean, unknown>(false, { transform: booleanAttribute });\n\n /** use the material style */\n readonly styleMode = input<'material' | 'noStyle' | 'borderless' | 'default'>('default');\n\n /** message when no result */\n readonly noResultMessage = input<string>();\n\n /** maximum results limit (0 = no limit) */\n readonly maxResults = input(0, { transform: numberAttribute });\n\n /** message when maximum results */\n readonly maxResultsMessage = input('Too many results…');\n\n /** infinite scroll distance */\n readonly infiniteScrollDistance = input(1.5, { transform: numberAttribute });\n\n /** infinite scroll distance */\n readonly infiniteScrollThrottle = input(150, { transform: numberAttribute });\n\n /** infinite scroll activated */\n readonly infiniteScroll = input<boolean, unknown>(false, { transform: booleanAttribute });\n\n /** auto create if not exist */\n readonly autoCreate = input<boolean, unknown>(false, { transform: booleanAttribute });\n\n /** no template for label selection */\n readonly noLabelTemplate = input<boolean, unknown>(false, { transform: booleanAttribute });\n\n /** use it for change the pattern of the filter search */\n readonly editPattern = input<((str: string) => string) | undefined>(undefined);\n\n /** template(s) for formatting */\n readonly templates = input<Select2Template>(undefined);\n\n /** template for formatting selected option */\n readonly templateSelection = input<TemplateRef<any> | undefined>(undefined);\n\n /** the max height of the results container when opening the select */\n readonly resultMaxHeight = input('200px');\n\n /** Active Search event */\n readonly customSearchEnabled = input<boolean, unknown>(false, { transform: booleanAttribute });\n\n /** minimal data of show the search field */\n readonly minCountForSearch = input(undefined, { transform: numberAttribute });\n\n /** Unique id of the element. */\n readonly id = input<string>(this._uid);\n\n /** Unique id of label element. */\n readonly idLabel = computed(() => `${this.id()}-label`);\n\n /** Unique id of combo element. */\n readonly idCombo = computed(() => `${this.id()}-combo`);\n\n /** Unique id of options element. */\n readonly idOptions = computed(() => `${this.id()}-options`);\n\n /** Unique id of overlay element. */\n readonly idOverlay = computed(() => `${this.id()}-overlay`);\n\n /** Whether the element is required. */\n readonly required = input<boolean, unknown>(false, { transform: booleanAttribute });\n\n /** Whether selected items should be hidden. */\n readonly disabled = input<boolean, unknown>(false, { transform: booleanAttribute });\n\n /** Whether items are hidden when has. */\n readonly hideSelectedItems = input<boolean, unknown>(false, { transform: booleanAttribute });\n\n /** Whether the element is readonly. */\n readonly readonly = input<boolean, unknown>(false, { transform: booleanAttribute });\n\n /** The input element's value. */\n readonly value = input<Select2UpdateValue>();\n\n /** Tab index for the select2 element. */\n readonly tabIndex = input(0, { transform: numberAttribute });\n\n /** reset with no selected value */\n readonly resettable = input<boolean, unknown>(false, { transform: booleanAttribute });\n\n /** selected value when × is clicked */\n readonly resetSelectedValue = input<any>(undefined);\n\n /** like native select keyboard navigation (only single mode) */\n readonly nativeKeyboard = input<boolean, unknown>(false, { transform: booleanAttribute });\n\n /** grid: item by line\n * * 0 = no grid\n * * number = item by line (4)\n * * string = minimal size item (100px)\n */\n readonly grid = input('');\n\n /**\n * Replace selection by a text\n * * if string: `%size%` = total selected options\n * * if function: juste show the string\n */\n readonly selectionOverride = input<Select2SelectionOverride | undefined>(undefined);\n\n /** force selection on one line */\n readonly selectionNoWrap = input<boolean, unknown>(false, { transform: booleanAttribute });\n\n /** Add an option to select or remove all (if all is selected) */\n readonly showSelectAll = input<boolean, unknown>(false, { transform: booleanAttribute });\n\n /** Text for remove all options */\n readonly removeAllText = input('Remove all');\n\n /** Text for select all options */\n readonly selectAllText = input('Select all');\n\n // -- WAI related inputs ---\n\n /** title attribute applied to the input */\n readonly title = input<string>();\n\n /** aria-labelledby attribute applied to the input, to specify en external label */\n readonly ariaLabelledby = input<string>();\n\n /** aria-describedby attribute applied to the input */\n readonly ariaDescribedby = input<string>();\n\n /** aria-invalid attribute applied to the input, to force error state */\n readonly ariaInvalid = input<boolean, unknown>(false, { transform: booleanAttribute });\n\n /** description of the reset button when using 'resettable'. Default value : 'Reset' */\n readonly ariaResetButtonDescription = input<string>('Reset');\n\n // ----------------------- output\n\n readonly update = output<Select2UpdateEvent<Select2UpdateValue>>();\n readonly autoCreateItem = output<Select2AutoCreateEvent<Select2UpdateValue>>();\n readonly open = output<Select2>();\n readonly close = output<Select2>();\n readonly focus = output<Select2>();\n readonly blur = output<Select2>();\n readonly search = output<Select2SearchEvent<Select2UpdateValue>>();\n readonly scroll = output<Select2ScrollEvent>();\n readonly removeOption = output<Select2RemoveEvent<Select2UpdateValue>>();\n\n // ----------------------- signal viewChild\n\n readonly cdkConnectedOverlay = viewChild.required(CdkConnectedOverlay);\n readonly selection = viewChild.required<ElementRef<HTMLElement>>('selection');\n readonly resultContainer = viewChild<ElementRef<HTMLElement>>('results');\n readonly results = viewChildren<ElementRef>('result');\n readonly searchInput = viewChild<ElementRef<HTMLElement>>('searchInput');\n readonly dropdown = viewChild<ElementRef<HTMLElement>>('dropdown');\n\n // ----------------------- HostBinding\n\n @HostBinding('class.material')\n get classMaterial(): boolean {\n return this.styleMode() === 'material';\n }\n\n @HostBinding('class.nostyle')\n get classNostyle(): boolean {\n return this.styleMode() === 'noStyle';\n }\n\n @HostBinding('class.borderless')\n get classBorderless(): boolean {\n return this.styleMode() === 'borderless';\n }\n\n @HostBinding('class.select2-above')\n get select2above(): boolean {\n return !this.overlay() ? this.listPosition() === 'above' : this._isAbobeOverlay();\n }\n\n // ----------------------- internal var\n\n selectedOption: Select2Option | Select2Option[] | null = null;\n isOpen = false;\n searchStyle: string | undefined;\n\n /** Whether the element is focused or not. */\n focused = false;\n\n filteredData = signal<Select2Data | undefined>(undefined);\n\n get select2Options(): Select2Option[] {\n return this.multiple() ? ((this.selectedOption as Select2Option[]) ?? []) : [];\n }\n\n get select2Option(): Select2Option | null {\n return this.multiple() ? null : (this.selectedOption as Select2Option);\n }\n\n get searchText() {\n return this.innerSearchText;\n }\n\n protected set searchText(text: string) {\n this.innerSearchText = text;\n }\n\n get disabledState() {\n return this._control?.disabled ?? this._disabled;\n }\n\n protected overlayWidth: number | string = '';\n protected overlayHeight: number | string = '';\n protected _triggerRect: DOMRect | undefined;\n protected _dropdownRect: DOMRect | undefined;\n\n protected get _positions(): any {\n switch (this.listPosition()) {\n case 'above':\n return [\n new ConnectionPositionPair(\n { originX: 'start', originY: 'top' },\n { overlayX: 'start', overlayY: 'bottom' },\n ),\n ];\n case 'auto':\n return [\n new ConnectionPositionPair(\n { originX: 'start', originY: 'bottom' },\n { overlayX: 'start', overlayY: 'top' },\n ),\n new ConnectionPositionPair(\n { originX: 'start', originY: 'top' },\n { overlayX: 'start', overlayY: 'bottom' },\n ),\n ];\n\n default:\n return [\n new ConnectionPositionPair(\n { originX: 'start', originY: 'bottom' },\n { overlayX: 'start', overlayY: 'top' },\n ),\n ];\n }\n }\n\n protected maxResultsExceeded: boolean | undefined;\n\n private hoveringOption = signal<Select2Option | null>(null);\n hoveringOptionId = computed(() => this.getElementId(this.hoveringOption()));\n\n private innerSearchText = '';\n protected isSearchboxHidden: boolean | undefined;\n\n private selectionElement: HTMLElement | undefined;\n\n private get resultsElement(): HTMLElement | undefined {\n return this.resultContainer()?.nativeElement;\n }\n\n private _stateChanges = new Subject<void>();\n\n /** Tab index for the element. */\n protected get _tabIndex(): number {\n return this.disabledState ? -1 : this.tabIndex();\n }\n\n private _data: Select2Data = [];\n\n private _disabled = false;\n\n protected _value: Select2UpdateValue | null = null;\n private _previousNativeValue: Select2UpdateValue | undefined;\n private _overlayPosition: VerticalConnectionPos | undefined;\n private toObservable = new Subscription();\n\n constructor(\n protected _viewportRuler: ViewportRuler,\n private _changeDetectorRef: ChangeDetectorRef,\n @Optional() private _parentForm: NgForm,\n @Optional() private _parentFormGroup: FormGroupDirective,\n @Self() @Optional() public _control: NgControl,\n @Attribute('tabindex') tabIndex: string,\n ) {\n if (this._control) {\n this._control.valueAccessor = this;\n }\n this.toObservable.add(\n toObservable(this.multiple).subscribe(_multiple => {\n this.ngOnInit();\n }),\n );\n this.toObservable.add(\n toObservable(this.minCountForSearch).subscribe(minCountForSearch => {\n this.updateSearchBox();\n }),\n );\n this.toObservable.add(\n toObservable(this.disabled).subscribe(disabled => {\n this._disabled = disabled;\n }),\n );\n }\n\n ngOnChanges(changes: SimpleChanges): void {\n let updateFilterData;\n if (changes['data']) {\n this._data = changes['data'].currentValue;\n updateFilterData = true;\n }\n if (changes['value']) {\n const value = changes['value'].currentValue;\n if (this.testValueChange(this._value, value)) {\n if (this._value === undefined) {\n this._value = value;\n }\n this.writeValue(value);\n updateFilterData = true;\n }\n }\n // now call update filter data so that _data and the current selected values are set\n if (updateFilterData) {\n this.updateFilteredData();\n }\n }\n\n @HostListener('document:click', ['$event'])\n clickDetection(e: MouseEvent) {\n if (this.isOpen) {\n const target = e.target as HTMLElement;\n if (!this.ifParentContainsClass(target, 'selection')) {\n if (\n (!this.ifParentContainsClass(target, 'select2-dropdown') &&\n !this.multiple() &&\n !this.hideSelectedItems()) ||\n !(\n this.ifParentContainsClass(target, 'select2-results__option') ||\n this.ifParentContainsClass(target, 'select2-dropdown')\n )\n ) {\n this.toggleOpenAndClose();\n }\n\n if (!this.overlay() && !this.ifParentContainsId(target, this.id())) {\n this.clickExit();\n }\n } else if (!this.ifParentContainsId(target, this.id())) {\n this.toggleOpenAndClose();\n this.clickExit();\n }\n } else if (this.focused) {\n const target = e.target as HTMLElement;\n this._focus(this.clickOnSelect2Element(target));\n }\n }\n\n /** View -> model callback called when select has been touched */\n private _onTouched = () => {\n // do nothing\n };\n\n /** View -> model callback called when value changes */\n private _onChange: (value: any) => void = () => {\n // do nothing\n };\n\n ngOnInit() {\n this._viewportRuler.change(100).subscribe(() => {\n if (this.isOpen) {\n this.triggerRect();\n }\n });\n\n const option = Select2Utils.getOptionsByValue(\n this._data,\n this._control ? this._control.value : this.value(),\n this.multiple(),\n );\n if (option !== null) {\n this.selectedOption = option ?? null;\n }\n if (!Array.isArray(option)) {\n this.hoveringOption.set(Select2Utils.getOptionByValue(this._data, this.value));\n }\n this.updateSearchBox();\n }\n\n ngAfterViewInit() {\n this.cdkConnectedOverlay().positionChange.subscribe((posChange: ConnectedOverlayPositionChange) => {\n if (\n this.listPosition() === 'auto' &&\n posChange.connectionPair?.originY &&\n this._overlayPosition !== posChange.connectionPair.originY\n ) {\n this.triggerRect();\n this._overlayPosition = posChange.connectionPair.originY;\n this._changeDetectorRef.detectChanges();\n }\n });\n\n this.selectionElement = this.selection().nativeElement;\n this.triggerRect();\n }\n\n ngDoCheck() {\n this.updateSearchBox();\n this._dirtyCheckNativeValue();\n if (this._triggerRect) {\n if (this.overlayWidth !== this._triggerRect.width) {\n this.overlayWidth = this._triggerRect.width;\n this._changeDetectorRef.detectChanges();\n }\n if (\n this._dropdownRect &&\n this._dropdownRect.height > 0 &&\n this.overlayHeight !== this._dropdownRect.height\n ) {\n this.overlayHeight = this.listPosition() === 'auto' ? this._dropdownRect.height : 0;\n this.fixValue();\n }\n }\n }\n\n ngOnDestroy(): void {\n this.toObservable.unsubscribe();\n }\n\n fixValue() {\n if (!Array.isArray(this.selectedOption) && this.multiple()) {\n const selectedOption = this.selectedOption;\n this.selectedOption = [];\n setTimeout(() => {\n this.select(selectedOption);\n this._changeDetectorRef.detectChanges();\n });\n } else if (Array.isArray(this.selectedOption) && !this.multiple()) {\n const selectedOption = this.selectedOption[0];\n this.selectedOption = null;\n setTimeout(() => {\n this.select(selectedOption);\n this._changeDetectorRef.detectChanges();\n });\n } else {\n this._changeDetectorRef.detectChanges();\n }\n }\n\n updateSearchBox() {\n if (this.autoCreate() && !this.multiple()) {\n this.isSearchboxHidden = false;\n } else {\n const hidden =\n this.displaySearchStatus() === 'hidden' ||\n (this.displaySearchStatus() !== 'always' &&\n !this.customSearchEnabled() &&\n Select2Utils.isSearchboxHidden(this._data, this.minCountForSearch()));\n\n if (this.isSearchboxHidden !== hidden) {\n this.isSearchboxHidden = hidden;\n }\n }\n }\n\n getOptionStyle(option: Select2Option) {\n return (\n 'select2-results__option ' +\n (option.hide ? 'select2-results__option--hide ' : '') +\n (option === this.hoveringOption() ? 'select2-results__option--highlighted ' : '') +\n (option.classes || '')\n );\n }\n\n mouseenter(option: Select2Option) {\n if (!option.disabled) {\n this.hoveringOption.set(option);\n }\n }\n\n click(option: Select2Option) {\n if (this.testSelection(option)) {\n this.select(option);\n }\n }\n\n reset(event?: MouseEvent) {\n const resetSelectedValue = this.resetSelectedValue();\n this.select(\n resetSelectedValue !== undefined\n ? (Select2Utils.getOptionByValue(this._data, resetSelectedValue) ?? null)\n : null,\n );\n\n if (event) {\n this.stopEvent(event);\n }\n this._focus(true);\n }\n\n prevChange(event: Event) {\n event.stopPropagation();\n }\n\n stopEvent(event: Event) {\n event.preventDefault();\n event.stopPropagation();\n }\n\n toggleOpenAndClose(focus = true, open?: boolean, event?: KeyboardEvent) {\n if (this.disabledState || this.readonly()) {\n return;\n }\n this._focus(focus);\n\n const onOpenAction = event && this._testKey(event, ON_OPEN_KEYS);\n const changeEmit = this.isOpen !== (open ?? !this.isOpen);\n this.isOpen = open ?? !this.isOpen;\n if (this.isOpen) {\n if (!this.isSearchboxHidden) {\n this.innerSearchText = '';\n this.updateFilteredData();\n this._focusSearchbox(focus);\n }\n\n if (this.isSearchboxHidden && !changeEmit && event) {\n this.keyDown(event);\n } else {\n if (this.selectedOption) {\n const option = Array.isArray(this.selectedOption) ? this.selectedOption[0] : this.selectedOption;\n this.updateScrollFromOption(option);\n } else if (this.resultsElement) {\n this.resultsElement.scrollTop = 0;\n }\n if (onOpenAction) {\n this.keyDown(event);\n }\n this._changeDetectorRef.detectChanges();\n\n this.triggerRect();\n this.cdkConnectedOverlay().overlayRef?.updatePosition();\n }\n if (changeEmit) {\n this.open.emit(this);\n }\n } else if (changeEmit) {\n this.close.emit(this);\n }\n\n this._changeDetectorRef.markForCheck();\n }\n\n hasTemplate(option: Select2Option | Select2Group, defaultValue: string, select: boolean = false) {\n const templates: any = this.templates();\n const templatesValue: any = this.templates();\n return (\n (select\n ? templates?.[(option as Select2Option).templateSelectionId ?? ''] instanceof TemplateRef ||\n templates?.[`${defaultValue}Selection`] instanceof TemplateRef ||\n templates?.[`templateSelection`] instanceof TemplateRef ||\n this.templateSelection() instanceof TemplateRef\n : false) ||\n templatesValue?.[option.templateId ?? ''] instanceof TemplateRef ||\n templatesValue?.[defaultValue] instanceof TemplateRef ||\n templatesValue?.['template'] instanceof TemplateRef ||\n templatesValue instanceof TemplateRef ||\n false\n );\n }\n\n getTemplate(option: Select2Option | Select2Group, defaultValue: string, select: boolean = false) {\n const templates: any = this.templates();\n const templatesValue: any = this.templates();\n return this.hasTemplate(option, defaultValue, select)\n ? (select\n ? templates?.[(option as Select2Option).templateSelectionId ?? ''] ||\n templates?.[`${defaultValue}Selection`] ||\n templates?.[`templateSelection`] ||\n this.templateSelection()\n : undefined) ||\n templatesValue?.[option.templateId ?? ''] ||\n templatesValue?.[defaultValue] ||\n templatesValue?.['template'] ||\n templatesValue\n : undefined;\n }\n\n triggerRect() {\n this._triggerRect = this.selectionElement?.getBoundingClientRect();\n const dropdown = this.dropdown();\n this._dropdownRect = dropdown?.nativeElement ? dropdown.nativeElement.getBoundingClientRect() : undefined;\n }\n\n isNumber(o: any): boolean {\n return !isNaN(o - 0) && o !== null && o !== '' && o !== false;\n }\n\n selectAll() {\n if (this.multiple()) {\n if (!this.selectAllTest()) {\n const options: Select2Option[] = [];\n this._data.forEach(e => {\n if (Select2Utils.isGroup(e)) {\n e.options.forEach(f => {\n if (!f.disabled && !f.hide) {\n options.push(f);\n }\n });\n } else if (!e.disabled && !e.hide) {\n options.push(e);\n }\n });\n this.selectedOption = options;\n this.writeValue(options.map(e => e.value));\n } else {\n this.selectedOption = [];\n this.writeValue([]);\n }\n\n this.isOpen = false;\n this.close.emit(this);\n }\n }\n\n selectAllTest() {\n if (this.multiple() && Array.isArray(this.selectedOption) && this.selectedOption.length) {\n let options = 0;\n this._data.forEach(e => {\n if (Select2Utils.isGroup(e)) {\n e.options.forEach(f => {\n if (!f.disabled && !f.hide) {\n options++;\n }\n });\n } else if (!e.disabled && !e.hide) {\n options++;\n }\n });\n return this.selectedOption.length === options;\n }\n return false;\n }\n\n private testSelection(option: Select2Option) {\n if (option.disabled) {\n return false;\n }\n\n const limitSelection = this.limitSelection();\n return !this.multiple() || !limitSelection || this.optionsSize() < limitSelection;\n }\n\n private testValueChange(value1: Select2UpdateValue | null, value2: Select2UpdateValue | undefined) {\n if (\n ((value1 === null || value1 === undefined) && (value2 === null || value2 === undefined)) ||\n value1 === value2\n ) {\n return false;\n }\n if (\n this.multiple() &&\n (value1 as Select2Value[])?.length &&\n (value2 as Select2Value[])?.length &&\n (value1 as Select2Value[]).length === (value2 as Select2Value[]).length\n ) {\n for (const e1 of value1 as Select2Value[]) {\n const test = (value2 as Select2Value[]).indexOf(e1) > -1;\n if (!test) {\n return true;\n }\n }\n return false;\n }\n return true;\n }\n\n private updateFilteredData() {\n let result = this._data;\n\n if (this.multiple() && this.hideSelectedItems()) {\n result = Select2Utils.getFilteredSelectedData(result, this.selectedOption);\n }\n if (!this.customSearchEnabled() && this.searchText && this.searchText.length >= +this.minCharForSearch()) {\n result = Select2Utils.getFilteredData(result, this.searchText, this.editPattern());\n }\n if (this.maxResults() > 0) {\n const data = Select2Utils.getReduceData(result, +this.maxResults());\n result = data.result;\n this.maxResultsExceeded = data.reduce;\n } else {\n this.maxResultsExceeded = false;\n }\n\n if (Select2Utils.optionIsNotInFilteredData(result, this.hoveringOption())) {\n this.hoveringOption.set(Select2Utils.getFirstAvailableOption(result));\n }\n\n this.filteredData.set(result);\n\n // replace selected options when data change\n\n if (this.multiple() && Array.isArray(this.selectedOption) && this.selectedOption.length) {\n const options: Select2Option[] = [];\n const value = this.selectedOption.map(e => e.value);\n this._data.forEach(e => {\n if (Select2Utils.isGroup(e)) {\n e.options.forEach(f => {\n if (value.includes(f.value)) {\n options.push(f);\n }\n });\n } else if (value.includes(e.value)) {\n options.push(e);\n }\n });\n // preserve selection order\n this.selectedOption = this.selectedOption.map(e => options.find(f => f.value === e.value)!);\n } else if (!Array.isArray(this.selectedOption) && this.selectedOption) {\n let option: Select2Option | null = null;\n this._data.forEach(e => {\n if (Select2Utils.isGroup(e)) {\n e.options.forEach(f => {\n if ((this.selectedOption as Select2Option).value === f.value) {\n option = f;\n }\n });\n } else if ((this.selectedOption as Select2Option).value === e.value) {\n option = e;\n }\n });\n this.selectedOption = option;\n }\n this._changeDetectorRef.detectChanges();\n }\n\n private clickExit() {\n this._focus(false);\n }\n\n private isInSelect(elt: Element): boolean {\n return this.ifParentContainsId(elt, this.id()) || this.ifParentContainsId(elt, this.idOverlay());\n }\n\n private ifParentContainsClass(element: Element, cssClass: string): boolean {\n return this.getParentElementByClass(element, cssClass) !== null;\n }\n\n private ifParentContainsId(element: Element, id: string): boolean {\n return this.getParentElementById(element, id) !== null;\n }\n\n private getParentElementByClass(element: Element, cssClass: string): Element | null {\n return this.containClasses(element, cssClass.trim().split(/\\s+/))\n ? element\n : element.parentElement\n ? this.getParentElementByClass(element.parentElement, cssClass)\n : null;\n }\n\n private getParentElementById(element: Element, id: string): Element | null {\n return element.id === id\n ? element\n : element.parentElement\n ? this.getParentElementById(element.parentElement, id)\n : null;\n }\n\n private containClasses(element: Element, cssClasses: string[]): boolean {\n if (!element.classList) {\n return false;\n }\n\n for (const cssClass of cssClasses) {\n if (!element.classList.contains(cssClass)) {\n return false;\n }\n }\n\n return true;\n }\n\n private containAlmostOneClasses(element: HTMLElement, cssClasses: string[]): boolean {\n if (!element.classList) {\n return false;\n }\n\n let containAlmostOne = false;\n for (const cssClass of cssClasses) {\n if (element.classList.contains(cssClass)) {\n containAlmostOne = true;\n }\n }\n\n return containAlmostOne;\n }\n\n private clickOnSelect2Element(element: HTMLElement): boolean {\n return this.containAlmostOneClasses(element, [\n 'select2-overlay-backdrop',\n 'select2-label-content',\n 'select2-selection__rendered',\n 'select2-results__option',\n ]);\n }\n\n focusin(options?: FocusOptions) {\n if (!this.disabledState) {\n this._focus(true, options);\n }\n }\n\n focusout(event: FocusEvent) {\n if (!event.relatedTarget || !this.isInSelect(event.relatedTarget as Element)) {\n this._focus(false);\n this._onTouched();\n }\n }\n\n select(option: Select2Option | null, emit: boolean = true, closeOnSelect: boolean = true) {\n let value: any;\n\n if (option !== null && option !== undefined) {\n if (this.multiple()) {\n this.selectedOption ??= [];\n const options = this.selectedOption as Select2Option[];\n const index = options.findIndex(op => op.value === option.value);\n if (index === -1) {\n options.push(option);\n } else {\n options.splice(index, 1);\n }\n\n value = (this.selectedOption as Select2Option[]).map(op => op.value);\n } else {\n this.selectedOption = option;\n if (closeOnSelect && this.isOpen) {\n this.isOpen = false;\n this.close.emit(this);\n this.selectionElement?.focus();\n }\n value = this.selectedOption.value;\n if (!option && this._value === null) {\n this._value = value;\n }\n }\n } else {\n // when remove value\n if (Array.isArray(this.selectedOption) ? this.selectedOption?.length : this.selectedOption) {\n value = '';\n }\n this.sele