ng-select2-component
Version:
An Angular select2 component.
1 lines • 183 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-content.directive.ts","../../../projects/ng-select2-component/src/lib/select2-option.directive.ts","../../../projects/ng-select2-component/src/lib/select2-group.directive.ts","../../../projects/ng-select2-component/src/lib/select2-utils.ts","../../../projects/ng-select2-component/src/lib/select2-highlight.pipe.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.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\n/**\n * Latin - `[\\u0300-\\u036F]` matches combining diacritical marks:\n * - `\\u0300-\\u036F`: grave, acute, circumflex, tilde, macron, breve, dot above, diaeresis, ring above, etc.\n */\nexport const latinDiacritical = { tmp: '\\uE000', pattern: '[\\\\u0300-\\\\u036F]*' };\n/**\n * Arabic - `[\\u064B-\\u0652\\u0670]` matches Arabic diacritics (harakat):\n * - `\\u064B-\\u0652`: fatha, damma, kasra, sukun, shadda, tanwin, etc.\n * - `\\u0670`: alif khanjariyah (superscript alif)\n */\nexport const arabicDiacritical = { tmp: '\\uE001', pattern: '[\\\\u064B-\\\\u0652\\\\u0670]*' };\n/**\n * Hebrew - `[\\u05B0-\\u05BC\\u05C1\\u05C2]` matches Hebrew diacritics (niqqud):\n * - `\\u05B0-\\u05BC`: vowel points (sheva, hataf, hiriq, tsere, segol, patah, qamats, holam, qubuts, dagesh, etc.)\n * - `\\u05C1-\\u05C2`: shin dot (right) and sin dot (left)\n */\nexport const hebrewDiacritical = { tmp: '\\uE002', pattern: '[\\\\u05B0-\\\\u05BC\\\\u05C1\\\\u05C2]*' };\n\nexport const unicodePatterns: { l: string; s: RegExp; e?: string; d?: { tmp: string; pattern: string } }[] = [\n // Latin\n { l: 'a', s: /[ⓐaẚàáâầấẫẩãāăằắẵẳȧǡäǟảåǻǎȁȃạậặḁąⱥɐ]/gi, e: 'a(?![aeouvy])', d: latinDiacritical },\n { l: 'aa', s: /ꜳ/gi, d: latinDiacritical },\n { l: 'ae', s: /[æǽǣ]/gi, d: latinDiacritical },\n { l: 'ao', s: /ꜵ/gi, d: latinDiacritical },\n { l: 'au', s: /ꜷ/gi, d: latinDiacritical },\n { l: 'av', s: /[ꜹꜻ]/gi, d: latinDiacritical },\n { l: 'ay', s: /ꜽ/gi, d: latinDiacritical },\n { l: 'b', s: /[ⓑbḃḅḇƀƃɓ]/gi, d: latinDiacritical },\n { l: 'c', s: /[ⓒcćĉċčçḉƈȼꜿↄ]/gi, d: latinDiacritical },\n { l: 'd', s: /[ⓓdḋďḍḑḓḏđƌɖɗꝺ]/gi, e: 'd(?!z)', d: latinDiacritical },\n { l: 'dz', s: /[dzdž]/gi, d: latinDiacritical },\n { l: 'e', s: /(?<!o)[eⓔeèéêềếễểẽēḕḗĕėëẻěȅȇẹệȩḝęḙḛɇɛǝ]/gi, e: '(?<!o)e', d: latinDiacritical },\n { l: 'f', s: /[ⓕfḟƒꝼ]/gi, d: latinDiacritical },\n { l: 'g', s: /[ⓖgǵĝḡğġǧģǥɠꞡᵹꝿ]/gi, d: latinDiacritical },\n { l: 'h', s: /[ⓗhĥḣḧȟḥḩḫẖħⱨⱶɥ]/gi, e: 'h(?!v)', d: latinDiacritical },\n { l: 'hv', s: /ƕ/gi, d: latinDiacritical },\n { l: 'i', s: /[ⓘiìíîĩīĭİïḯỉǐȉȋịįḭɨı]/gi, d: latinDiacritical },\n { l: 'j', s: /[ⓙjĵǰɉ]/gi, e: '(?<![ln])j', d: latinDiacritical },\n { l: 'k', s: /[ⓚkḱǩḳķḵƙⱪꝁꝃꝅꞣ]/gi, d: latinDiacritical },\n { l: 'l', s: /[ⓛlŀĺľḷḹļḽḻſłƚɫⱡꝉꞁꝇꝆ]/gi, e: 'l(?!j)', d: latinDiacritical },\n { l: 'lj', s: /lj/gi, d: latinDiacritical },\n { l: 'm', s: /[ⓜmḿṁṃɱɯ]/gi, d: latinDiacritical },\n { l: 'n', s: /[ⓝnǹńñṅňṇņṋṉƞɲʼnꞑꞥ]/gi, e: 'n(?!j)', d: latinDiacritical },\n { l: 'nj', s: /nj/gi, d: latinDiacritical },\n { l: 'o', s: /[ⓞoòóôồốỗổõṍȭṏōṑṓŏȯȱöȫỏőǒȍȏơờớỡởợọộǫǭøǿɔƟꝋꝍɵ]/gi, e: 'o(?![ieou])', d: latinDiacritical },\n { l: 'oi', s: /ƣ/gi, d: latinDiacritical },\n { l: 'oe', s: /œ/gi, d: latinDiacritical },\n { l: 'oo', s: /ꝏ/gi, d: latinDiacritical },\n { l: 'ou', s: /ȣ/gi, d: latinDiacritical },\n { l: 'p', s: /[ⓟpṕṗƥᵽꝑꝓꝕ]/gi, d: latinDiacritical },\n { l: 'q', s: /[ⓠqɋꝗꝙ]/gi, d: latinDiacritical },\n { l: 'r', s: /[ⓡrŕṙřȑȓṛṝŗṟɍɽꝛꞧꞃ]/gi, d: latinDiacritical },\n { l: 's', s: /[ⓢsßẞśṥŝṡšṧṣṩșşȿꞩꞅẛ]/gi, d: latinDiacritical },\n { l: 't', s: /[ⓣtṫẗťṭțţṱṯŧƭʈⱦꞇ]/gi, e: 't(?!z)', d: latinDiacritical },\n { l: 'tz', s: /ꜩ/gi, d: latinDiacritical },\n { l: 'u', s: /[ⓤuùúûũṹūṻŭüǜǘǖǚủůűǔȕȗưừứữửựụṳųṷṵʉ]/gi, d: latinDiacritical },\n { l: 'v', s: /[ⓥvṽṿʋꝟʌ]/gi, e: 'v(?!y)', d: latinDiacritical },\n { l: 'vy', s: /ꝡ/gi, d: latinDiacritical },\n { l: 'w', s: /[ⓦwẁẃŵẇẅẘẉⱳ]/gi, d: latinDiacritical },\n { l: 'x', s: /[ⓧxẋẍ]/gi, d: latinDiacritical },\n { l: 'y', s: /[ⓨyỳýŷỹȳẏÿỷẙỵƴɏỿ]/gi, d: latinDiacritical },\n { l: 'z', s: /[ⓩzźẑżžẓẕƶȥɀⱬꝣ]/gi, d: latinDiacritical },\n // Japanese - Hiragana/Katakana\n { l: 'あ', s: /[あアぁァ]゙?/gi },\n { l: 'い', s: /[いイぃィ]゙?/gi },\n { l: 'う', s: /[うウぅゥゔヴ]゙?/gi },\n { l: 'え', s: /[えエぇェ]゙?/gi },\n { l: 'お', s: /[おオぉォ]゙?/gi },\n { l: 'か', s: /[かカがガゕヵ]゙?/gi },\n { l: 'き', s: /[きキぎギ]゙?/gi },\n { l: 'く', s: /[くクぐグ]゙?/gi },\n { l: 'け', s: /[けケげゲゖヶ]゙?/gi },\n { l: 'こ', s: /[こコごゴ]゙?/gi },\n { l: 'さ', s: /[さサざザ]゙?/gi },\n { l: 'し', s: /[しシじジ]゙?/gi },\n { l: 'す', s: /[すスずズ]゙?/gi },\n { l: 'せ', s: /[せセぜゼ]゙?/gi },\n { l: 'そ', s: /[そソぞゾ]゙?/gi },\n { l: 'た', s: /[たタだダ]゙?/gi },\n { l: 'ち', s: /[ちチぢヂ]゙?/gi },\n { l: 'つ', s: /[つツっッづヅ]゙?/gi },\n { l: 'て', s: /[てテでデ]゙?/gi },\n { l: 'と', s: /[とトどド]゙?/gi },\n { l: 'な', s: /ナ/gi },\n { l: 'に', s: /ニ/gi },\n { l: 'ぬ', s: /ヌ/gi },\n { l: 'ね', s: /ネ/gi },\n { l: 'の', s: /ノ/gi },\n { l: 'は', s: /[はハばバぱパ][゙゚]?/gi },\n { l: 'ひ', s: /[ひヒびビぴピ][゙゚]?/gi },\n { l: 'ふ', s: /[ふフぶブぷプ][゙゚]?/gi },\n { l: 'へ', s: /[へヘべベぺペ][゙゚]?/gi },\n { l: 'ほ', s: /[ほホぼボぽポ][゙゚]?/gi },\n { l: 'ま', s: /マ/gi },\n { l: 'み', s: /ミ/gi },\n { l: 'む', s: /ム/gi },\n { l: 'め', s: /メ/gi },\n { l: 'も', s: /モ/gi },\n { l: 'や', s: /[ヤゃャ]/gi },\n { l: 'ゆ', s: /[ユゅュ]/gi },\n { l: 'よ', s: /[ヨょョ]/gi },\n { l: 'ら', s: /ラ/gi },\n { l: 'り', s: /リ/gi },\n { l: 'る', s: /ル/gi },\n { l: 'れ', s: /レ/gi },\n { l: 'ろ', s: /ロ/gi },\n { l: 'わ', s: /[わワゎヮヷ]゙?/gi },\n { l: 'ゐ', s: /[ゐヰヸ]゙?/gi },\n { l: 'ゑ', s: /[ゑヱヹ]゙?/gi },\n { l: 'を', s: /[をヲヺ]゙?/gi },\n { l: 'ん', s: /[ン]/gi },\n // Cyrillic\n { l: 'а', s: /[аӐӑӒӓӔӕ]/gi },\n { l: 'г', s: /[гѓґҐғҒҕҔ]/gi },\n { l: 'д', s: /[дђѐ]/gi },\n { l: 'е', s: /[еѐёЁӖӗ]/gi },\n { l: 'ж', s: /[жӁӂӜӝ]/gi },\n { l: 'з', s: /[зӞӟ]/gi },\n { l: 'и', s: /[иѝӢӣӤӥ]/gi },\n { l: 'й', s: /[йЙ]/gi },\n { l: 'к', s: /[кќҚқҜҝҞҟҠҡ]/gi },\n { l: 'л', s: /[лљЉ]/gi },\n { l: 'н', s: /[нњЊҢңӉӊ]/gi },\n { l: 'о', s: /[оӦӧӨӫ]/gi },\n { l: 'с', s: /[сҪҫ]/gi },\n { l: 'т', s: /[тћЋ]/gi },\n { l: 'у', s: /[уўЎӮӯӰӱӲӳ]/gi },\n { l: 'х', s: /[хҲҳӇӈ]/gi },\n { l: 'ч', s: /[чҴҵӴӵ]/gi },\n { l: 'ы', s: /[ыӸӹ]/gi },\n // Greek\n { l: 'α', s: /[αάἀἁἂἃἄἅἆἇὰάᾀᾁᾂᾃᾄᾅᾆᾇᾰᾱᾲᾳᾴᾶᾷ]/gi },\n { l: 'β', s: /[βϐ]/gi },\n { l: 'η', s: /[ηήἠἡἢἣἤἥἦἧὴήᾐᾑᾒᾓᾔᾕᾖᾗῂῃῄῆῇ]/gi },\n { l: 'θ', s: /[θϑ]/gi },\n { l: 'ι', s: /[ιίϊΐἰἱἲἳἴἵἶἷὶίῐῑῒΐῖῗ]/gi },\n { l: 'κ', s: /[κϰ]/gi },\n { l: 'ο', s: /[οόὀὁὂὃὄὅὸό]/gi },\n { l: 'π', s: /[πϖ]/gi },\n { l: 'ρ', s: /[ρϱῤῥ]/gi },\n { l: 'σ', s: /[σςϲ]/gi },\n { l: 'υ', s: /[υύϋΰὐὑὒὓὔὕὖὗὺύῠῡῢΰῦῧ]/gi },\n { l: 'φ', s: /[φϕ]/gi },\n { l: 'ω', s: /[ωώὠὡὢὣὤὥὦὧὼώᾠᾡᾢᾣᾤᾥᾦᾧῲῳῴῶῷ]/gi },\n // Arabic\n { l: 'ا', s: /[اأإآٱ]/gi, d: arabicDiacritical },\n { l: 'ب', s: /ب/gi, d: arabicDiacritical },\n { l: 'ت', s: /[تة]/gi, d: arabicDiacritical },\n { l: 'ث', s: /ث/gi, d: arabicDiacritical },\n { l: 'ج', s: /ج/gi, d: arabicDiacritical },\n { l: 'ح', s: /ح/gi, d: arabicDiacritical },\n { l: 'خ', s: /خ/gi, d: arabicDiacritical },\n { l: 'د', s: /د/gi, d: arabicDiacritical },\n { l: 'ذ', s: /ذ/gi, d: arabicDiacritical },\n { l: 'ر', s: /ر/gi, d: arabicDiacritical },\n { l: 'ز', s: /ز/gi, d: arabicDiacritical },\n { l: 'س', s: /س/gi, d: arabicDiacritical },\n { l: 'ش', s: /ش/gi, d: arabicDiacritical },\n { l: 'ص', s: /ص/gi, d: arabicDiacritical },\n { l: 'ض', s: /ض/gi, d: arabicDiacritical },\n { l: 'ط', s: /ط/gi, d: arabicDiacritical },\n { l: 'ظ', s: /ظ/gi, d: arabicDiacritical },\n { l: 'ع', s: /ع/gi, d: arabicDiacritical },\n { l: 'غ', s: /غ/gi, d: arabicDiacritical },\n { l: 'ف', s: /ف/gi, d: arabicDiacritical },\n { l: 'ق', s: /ق/gi, d: arabicDiacritical },\n { l: 'ك', s: /[كک]/gi, d: arabicDiacritical },\n { l: 'ل', s: /ل/gi, d: arabicDiacritical },\n { l: 'م', s: /م/gi, d: arabicDiacritical },\n { l: 'ن', s: /ن/gi, d: arabicDiacritical },\n { l: 'ه', s: /[هە]/gi, d: arabicDiacritical },\n { l: 'و', s: /[وؤ]/gi, d: arabicDiacritical },\n { l: 'ي', s: /[يىئ]/gi, d: arabicDiacritical },\n // Hebrew\n { l: 'א', s: /א/gi, d: hebrewDiacritical },\n { l: 'ב', s: /ב/gi, d: hebrewDiacritical },\n { l: 'ג', s: /ג/gi, d: hebrewDiacritical },\n { l: 'ד', s: /ד/gi, d: hebrewDiacritical },\n { l: 'ה', s: /ה/gi, d: hebrewDiacritical },\n { l: 'ו', s: /ו/gi, d: hebrewDiacritical },\n { l: 'ז', s: /ז/gi, d: hebrewDiacritical },\n { l: 'ח', s: /ח/gi, d: hebrewDiacritical },\n { l: 'ט', s: /ט/gi, d: hebrewDiacritical },\n { l: 'י', s: /י/gi, d: hebrewDiacritical },\n { l: 'כ', s: /[כך]/gi, d: hebrewDiacritical },\n { l: 'ל', s: /ל/gi, d: hebrewDiacritical },\n { l: 'מ', s: /[םמ]/gi, d: hebrewDiacritical },\n { l: 'נ', s: /[ןנ]/gi, d: hebrewDiacritical },\n { l: 'ס', s: /ס/gi, d: hebrewDiacritical },\n { l: 'ע', s: /ע/gi, d: hebrewDiacritical },\n { l: 'פ', s: /[פף]/gi, d: hebrewDiacritical },\n { l: 'צ', s: /[ץצ]/gi, d: hebrewDiacritical },\n { l: 'ק', s: /ק/gi, d: hebrewDiacritical },\n { l: 'ר', s: /ר/gi, d: hebrewDiacritical },\n { l: 'ש', s: /ש/gi, d: hebrewDiacritical },\n { l: 'ת', s: /ת/gi, d: hebrewDiacritical },\n];\n\nexport const defaultMinCountForSearch = 6;\n\nexport const protectRegexp = new RegExp('[\\\\-\\\\[\\\\]\\\\/\\\\{\\\\}\\\\(\\\\)\\\\*\\\\+\\\\?\\\\.\\\\\\\\\\\\^\\\\$\\\\|]', 'g');\n","import { Directive, ElementRef, inject, input, signal } from '@angular/core';\n\n/**\n * Shared base for the <ng-option> and <ng-group> directives.\n *\n * Holds the inputs common to both (classes, templateId, data, dir) and the projected-content\n * reactivity: plain text content (interpolation in the element body) is not a tracked signal, so\n * the host component dirty-checks it in ngDoCheck and pushes changes into {@link _projectedContent},\n * which the rebuild effect depends on through {@link _resolveLabel}.\n */\n@Directive()\nexport abstract class Select2ContentDirective {\n protected readonly _elementRef = inject(ElementRef<HTMLElement>);\n\n /** Additional CSS classes */\n readonly classes = input<string | undefined>(undefined);\n\n /** Template id */\n readonly templateId = input<string | undefined>(undefined);\n\n /** Arbitrary data attached to the option/group */\n readonly data = input<any>(undefined);\n\n /** Force text direction */\n readonly dir = input<'ltr' | 'rtl' | undefined>(undefined);\n\n /**\n * Reactive trigger for the projected text content (interpolation in the element body).\n * The host component dirty-checks the DOM in its ngDoCheck and updates this signal when the\n * rendered text changes, so the component's rebuild effect re-runs even though plain text\n * content is not otherwise a tracked dependency.\n */\n readonly _projectedContent = signal<string | undefined>(undefined);\n\n /** Read the host element's rendered text content (innerHTML, then textContent, then ''). */\n private _readContent(): string {\n const el = this._elementRef.nativeElement as HTMLElement;\n return el.innerHTML?.trim() || el.textContent?.trim() || '';\n }\n\n /**\n * Re-read the host element's rendered content and update {@link _projectedContent} when it\n * changed. Returns true if a change was detected. Called from the host component's ngDoCheck.\n */\n _refreshProjectedContent(): boolean {\n const content = this._readContent();\n if (content !== this._projectedContent()) {\n this._projectedContent.set(content);\n return true;\n }\n return false;\n }\n\n /**\n * Resolve the label: prefer the explicit [label] input, then the cached projected content,\n * then a one-off DOM read. Reading _projectedContent() registers it as a dependency of the\n * rebuild effect so interpolation/content changes propagate; the cached value avoids a second\n * DOM read, and _readContent() only runs on the initial pass.\n */\n protected _resolveLabel(label: string | undefined): string {\n return label ?? this._projectedContent() ?? this._readContent();\n }\n}\n","import { Directive, booleanAttribute, input } from '@angular/core';\n\nimport { Select2ContentDirective } from './select2-content.directive';\nimport { Select2Option, Select2Value } from './select2-interfaces';\n\n/**\n * Directive representing a single option inside a <ng-select2> or <ng-group>.\n *\n * Usage:\n * ```html\n * <ng-select2>\n * <ng-option value=\"foo\">Foo</ng-option>\n * <ng-option value=\"bar\" [disabled]=\"true\">Bar</ng-option>\n * </ng-select2>\n * ```\n */\n@Directive({ selector: 'ng-option' })\nexport class Select2OptionDirective extends Select2ContentDirective {\n /** The option value */\n readonly value = input.required<Select2Value>();\n\n /** Explicit label — falls back to the element's text content if omitted */\n readonly label = input<string | undefined>(undefined);\n\n /** Whether the option is disabled */\n readonly disabled = input<boolean, unknown>(false, { transform: booleanAttribute });\n\n /** Template selection id */\n readonly templateSelectionId = input<string | undefined>(undefined);\n\n /** Hide this option */\n readonly hide = input<boolean, unknown>(false, { transform: booleanAttribute });\n\n /** Build a plain Select2Option object from the current input values */\n toOption(): Select2Option {\n return {\n value: this.value(),\n label: this._resolveLabel(this.label()),\n disabled: this.disabled() || undefined,\n classes: this.classes(),\n templateId: this.templateId(),\n templateSelectionId: this.templateSelectionId(),\n data: this.data(),\n hide: this.hide() || undefined,\n dir: this.dir(),\n };\n }\n}\n","import { Directive, contentChildren, input } from '@angular/core';\n\nimport { Select2ContentDirective } from './select2-content.directive';\nimport { Select2Group } from './select2-interfaces';\nimport { Select2OptionDirective } from './select2-option.directive';\n\n/**\n * Directive representing an option group inside a <ng-select2>.\n *\n * Usage:\n * ```html\n * <ng-select2>\n * <ng-group label=\"Fruits\">\n * <ng-option value=\"apple\">Apple</ng-option>\n * <ng-option value=\"banana\">Banana</ng-option>\n * </ng-group>\n * </ng-select2>\n * ```\n */\n@Directive({ selector: 'ng-group' })\nexport class Select2GroupDirective extends Select2ContentDirective {\n /** The group label (required) */\n readonly label = input.required<string>();\n\n /** Child <ng-option> directives nested inside this group */\n readonly _ngOptions = contentChildren(Select2OptionDirective);\n\n /** Build a plain Select2Group object from the current input values */\n toGroup(): Select2Group {\n return {\n label: this._resolveLabel(this.label()),\n options: this._ngOptions().map(o => o.toOption()),\n classes: this.classes(),\n templateId: this.templateId(),\n data: this.data(),\n dir: this.dir(),\n };\n }\n}\n","import {\n arabicDiacritical,\n defaultMinCountForSearch,\n hebrewDiacritical,\n latinDiacritical,\n protectRegexp,\n unicodePatterns,\n} from './select2-const';\nimport { Select2Data, Select2Group, Select2Option, Select2UpdateValue, Select2Value } from './select2-interfaces';\n\nexport class Select2Utils {\n static getOptionByValue(data: Select2Data, value: Select2Value) {\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 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 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 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 {\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 {\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;\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,\n editPattern?: (str: string) => string,\n ): boolean {\n return (\n Select2Utils.formatSansUnicode(label).match(\n new RegExp(Select2Utils.formatPattern(searchText, editPattern), 'i'),\n ) !== null\n );\n }\n\n 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 static patternUnicode(str: string): string {\n for (const unicodePattern of unicodePatterns) {\n const pattern = unicodePattern.s.toString().replace('/gi', '').substring(1);\n str = str.replace(\n new RegExp(\n `((${unicodePattern.e ?? unicodePattern.l}|${pattern})${unicodePattern.d?.pattern ?? ''})`,\n 'gi',\n ),\n `((${unicodePattern.l}|${pattern})${unicodePattern.d?.tmp ?? ''})`,\n );\n }\n\n str = str\n .replaceAll(latinDiacritical.tmp, latinDiacritical.pattern)\n .replaceAll(arabicDiacritical.tmp, arabicDiacritical.pattern)\n .replaceAll(hebrewDiacritical.tmp, hebrewDiacritical.pattern);\n\n return str;\n }\n\n static formatPattern(str: string, editPattern?: (str: string) => string): 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 { Pipe, PipeTransform, inject } from '@angular/core';\nimport { DomSanitizer, SafeHtml } from '@angular/platform-browser';\n\nimport { Select2Utils } from './select2-utils';\n\n@Pipe({ name: 'highlightText' })\nexport class Select2HighlightPipe implements PipeTransform {\n private readonly sanitizer = inject(DomSanitizer);\n\n transform(\n value: string | null | undefined,\n search: string | null | undefined,\n disabled: boolean = false,\n ): SafeHtml | string {\n if (!value || !search || disabled) {\n return value ?? '';\n }\n const escapedSearch = Select2Utils.patternUnicode(Select2Utils.protectPattern(search));\n const result = value.replace(\n new RegExp(`(${escapedSearch})`, 'gi'),\n '<span class=\"select2-highlight-text\">$1</span>',\n );\n return this.sanitizer.bypassSecurityTrustHtml(result);\n }\n}\n","import { Directive } from '@angular/core';\n\n@Directive({ selector: 'select2-hint, ng-select2-hint' })\nexport class Select2Hint {}\n","import { Directive } from '@angular/core';\n\n@Directive({ selector: 'select2-label, ng-select2-label' })\nexport class Select2Label {}\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 ChangeDetectorRef,\n Component,\n DoCheck,\n ElementRef,\n OnChanges,\n OnDestroy,\n OnInit,\n SimpleChanges,\n TemplateRef,\n booleanAttribute,\n computed,\n contentChildren,\n effect,\n inject,\n input,\n numberAttribute,\n output,\n signal,\n untracked,\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 { Subject, Subscription, debounceTime, fromEvent } from 'rxjs';\n\nimport { Select2GroupDirective } from './select2-group.directive';\nimport { Select2HighlightPipe } from './select2-highlight.pipe';\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 { Select2OptionDirective } from './select2-option.directive';\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 imports: [CdkOverlayOrigin, NgTemplateOutlet, CdkConnectedOverlay, CdkDropList, CdkDrag, Select2HighlightPipe],\n host: {\n '[id]': 'id()',\n '[class.select2-selection-nowrap]': 'selectionNoWrap()',\n '[class.material]': 'classMaterial()',\n '[class.nostyle]': 'classNostyle()',\n '[class.borderless]': 'classBorderless()',\n '[class.select2-above]': 'select2above()',\n '(document:click)': 'clickDetection($event)',\n },\n})\nexport class Select2 implements ControlValueAccessor, OnInit, DoCheck, AfterViewInit, OnDestroy, OnChanges {\n protected _viewportRuler = inject(ViewportRuler);\n private _changeDetectorRef = inject(ChangeDetectorRef);\n private _parentForm = inject(NgForm, { optional: true });\n private _parentFormGroup = inject(FormGroupDirective, { optional: true });\n public _control = inject(NgControl, { optional: true, self: true });\n\n readonly _uid = `select2-${nextUniqueId++}`;\n // ----------------------- signal-input\n\n /** data of options & option groups */\n readonly data = input<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 /** highlight search text */\n readonly highlightText = 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 /** Show a checkbox next to each option */\n readonly showOptionCheckbox = 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 // ----------------------- content children (ng-option / ng-group template mode)\n\n /** Top-level <ng-option> elements (not inside a <ng-group>) */\n readonly _ngOptions = contentChildren(Select2OptionDirective);\n\n /** <ng-group> elements */\n readonly _ngGroups = contentChildren(Select2GroupDirective);\n\n // ----------------------- internal var\n\n readonly classMaterial = computed(() => this.styleMode() === 'material');\n\n readonly classNostyle = computed(() => this.styleMode() === 'noStyle');\n\n readonly classBorderless = computed(() => this.styleMode() === 'borderless');\n\n readonly select2above = computed(() =>\n !this.overlay() ? this.listPosition() === 'above' : this._isAbobeOverlay(),\n );\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 readonly _positions = computed(() => {\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 const container = this.resultContainer();\n return container ? container.nativeElement : undefined;\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 private _destroyed = false;\n\n protected _value: Select2UpdateValue | null = null;\n private _previousNativeValue: Select2UpdateValue | undefined;\n private _overlayPosition = signal<VerticalConnectionPos | undefined>(undefined);\n private toObservable = new Subscription();\n\n constructor() {\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(() => {\n this.updateSearchBox();\n }),\n );\n this.toObservable.add(\n toObservable(this.disabled).subscribe(disabled => {\n this._disabled = disabled;\n }),\n );\n // Rebuild _data whenever content children or any of their inputs change (template mode).\n // effect() re-runs synchronously in Angular's reactive context whenever any signal it\n // reads changes — including contentChildren signals and every input() of each directive.\n // untracked() isolates the side-effect (updateFilteredData reads many other signals)\n // so only _ngGroups/_ngOptions and the directive inputs are tracked dependencies.\n effect(() => {\n const grps = this._ngGroups();\n const opts = this._ngOptions();\n if (grps.length === 0 && opts.length === 0) {\n return;\n }\n\n // Read directive input signals here (tracked zone) so the effect re-runs\n // when any input changes. Guard against required inputs not yet initialized\n // (NG0950) by wrapping in try/catch — the effect will re-run once bindings resolve.\n let data: Select2Data;\n try {\n data = [\n ...grps.map(g => g.toGroup()),\n ...opts\n .filter(o => !grps.some(g => (g._ngOptions() as readonly Select2OptionDirective[]).includes(o)))\n .map(o => o.toOption()),\n ];\n } catch {\n // Required inputs not yet available — skip this run, effect will re-trigger\n return;\n }\n\n untracked(() => {\n this._data = data;\n this.updateFilteredData();\n // If selectedOption was never initialized (ngOnInit ran before content children\n // were available), resolve it now from the freshly populated _data.\n if (this.selectedOption === null) {\n const controlValue = this._control ? this._control.value : this.value();\n const option = Select2Utils.getOptionsByValue(this._data, controlValue, this.multiple());\n if (option !== null) {\n this.selectedOption = option;\n }\n this.hoveringOption.set(Select2Utils.getOptionByValue(this._data, this.value));\n }\n // Use a microtask to schedule change detection after Angular completes\n // its current initialization cycle. This avoids issues on direct page reload\n // where detectChanges() during effect() can fail silently.\n Promise.resolve().then(() => {\n if (!this._destroyed) {\n this._changeDetectorRef.markForCheck();\n }\n });\n });\n });\n }\n\n ngOnChanges(changes: SimpleChanges): void {\n let updateFilterData;\n if (changes['data']) {\n // Only use the bound data if no content children are present (template mode takes priority)\n if (this._ngOptions().length === 0 && this._ngGroups().length === 0) {\n this._data = changes['data'].currentValue;\n }\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 === null) {\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 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.multip