angular-perfect-select 2.2.0 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,12 +1,12 @@
1
1
  import * as i0 from '@angular/core';
2
- import { EventEmitter, HostListener, Output, Directive, signal, computed, SecurityContext, TemplateRef, forwardRef, ContentChild, ViewChild, Input, Component } from '@angular/core';
3
- import * as i2 from '@angular/common';
4
- import { CommonModule } from '@angular/common';
5
- import * as i3 from '@angular/forms';
2
+ import { EventEmitter, HostListener, Output, Directive, inject, PLATFORM_ID, signal, computed, Injectable, effect, SecurityContext, TemplateRef, forwardRef, ContentChild, ViewChild, Input, Component, makeEnvironmentProviders, importProvidersFrom } from '@angular/core';
3
+ import * as i3 from '@angular/common';
4
+ import { isPlatformBrowser, CommonModule } from '@angular/common';
5
+ import * as i4 from '@angular/forms';
6
6
  import { FormsModule, NG_VALUE_ACCESSOR } from '@angular/forms';
7
- import * as i4 from '@angular/cdk/scrolling';
7
+ import * as i5 from '@angular/cdk/scrolling';
8
8
  import { ScrollingModule, CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
9
- import * as i5 from '@angular/cdk/drag-drop';
9
+ import * as i6 from '@angular/cdk/drag-drop';
10
10
  import { moveItemInArray, DragDropModule } from '@angular/cdk/drag-drop';
11
11
  import { trigger, transition, style, animate, query, stagger } from '@angular/animations';
12
12
  import * as i1 from '@angular/platform-browser';
@@ -121,8 +121,227 @@ const THEMES = {
121
121
  }
122
122
  };
123
123
 
124
+ /**
125
+ * Fuzzy search utility for flexible string matching
126
+ * Supports acronym-style matching (e.g., 'fb' matches 'Facebook')
127
+ * and partial substring matching with scoring
128
+ */
129
+ /**
130
+ * Performs fuzzy search matching
131
+ * @param searchTerm The search query
132
+ * @param targetString The string to search in
133
+ * @param options Configuration options
134
+ * @returns Match result with score and matched character indices
135
+ */
136
+ function fuzzyMatch(searchTerm, targetString, options = {}) {
137
+ const { caseSensitive = false, threshold = 0 } = options;
138
+ // Normalize strings
139
+ const search = caseSensitive ? searchTerm : searchTerm.toLowerCase();
140
+ const target = caseSensitive ? targetString : targetString.toLowerCase();
141
+ if (search.length === 0) {
142
+ return { matches: true, score: 1, matchedIndices: [] };
143
+ }
144
+ if (target.length === 0) {
145
+ return { matches: false, score: 0, matchedIndices: [] };
146
+ }
147
+ // Check for exact match first (highest score)
148
+ if (target === search) {
149
+ return {
150
+ matches: true,
151
+ score: 1.0,
152
+ matchedIndices: Array.from({ length: search.length }, (_, i) => i)
153
+ };
154
+ }
155
+ // Check for substring match (high score)
156
+ const substringIndex = target.indexOf(search);
157
+ if (substringIndex !== -1) {
158
+ const score = 0.8 - (substringIndex * 0.01); // Earlier matches score higher
159
+ return {
160
+ matches: true,
161
+ score: Math.max(0.5, score),
162
+ matchedIndices: Array.from({ length: search.length }, (_, i) => substringIndex + i)
163
+ };
164
+ }
165
+ // Fuzzy matching algorithm (supports acronyms and scattered matches)
166
+ let searchIndex = 0;
167
+ let matchedIndices = [];
168
+ let consecutiveMatches = 0;
169
+ let totalScore = 0;
170
+ for (let targetIndex = 0; targetIndex < target.length; targetIndex++) {
171
+ if (search[searchIndex] === target[targetIndex]) {
172
+ matchedIndices.push(targetIndex);
173
+ consecutiveMatches++;
174
+ // Bonus points for consecutive matches
175
+ totalScore += consecutiveMatches > 1 ? 2 : 1;
176
+ // Bonus for matching at word boundaries
177
+ if (targetIndex === 0 || target[targetIndex - 1] === ' ' || target[targetIndex - 1] === '-') {
178
+ totalScore += 2;
179
+ }
180
+ searchIndex++;
181
+ if (searchIndex === search.length) {
182
+ break;
183
+ }
184
+ }
185
+ else {
186
+ consecutiveMatches = 0;
187
+ }
188
+ }
189
+ // Check if all search characters were found
190
+ const matches = searchIndex === search.length;
191
+ if (!matches) {
192
+ return { matches: false, score: 0, matchedIndices: [] };
193
+ }
194
+ // Calculate final score (0-1 range, excluding exact/substring matches)
195
+ const maxPossibleScore = search.length * 4; // Max points if all chars match at word boundaries consecutively
196
+ let normalizedScore = totalScore / maxPossibleScore;
197
+ // Penalty for scattered matches
198
+ const spreadPenalty = (matchedIndices[matchedIndices.length - 1] - matchedIndices[0]) / target.length;
199
+ normalizedScore *= (1 - spreadPenalty * 0.3);
200
+ // Cap fuzzy matches below substring matches
201
+ normalizedScore = Math.min(0.49, normalizedScore);
202
+ // Apply threshold
203
+ if (normalizedScore < threshold) {
204
+ return { matches: false, score: 0, matchedIndices: [] };
205
+ }
206
+ return { matches, score: normalizedScore, matchedIndices };
207
+ }
208
+ /**
209
+ * Sorts options by fuzzy match score
210
+ */
211
+ function sortByFuzzyScore(items, searchTerm, getLabel, options) {
212
+ if (!searchTerm) {
213
+ return items;
214
+ }
215
+ const itemsWithScores = items.map(item => {
216
+ const label = getLabel(item);
217
+ const result = fuzzyMatch(searchTerm, label, options);
218
+ return { item, score: result.score, matches: result.matches };
219
+ });
220
+ // Filter non-matches and sort by score descending
221
+ return itemsWithScores
222
+ .filter(({ matches }) => matches)
223
+ .sort((a, b) => b.score - a.score)
224
+ .map(({ item }) => item);
225
+ }
226
+
227
+ /**
228
+ * Utility functions for sorting select options
229
+ */
230
+ /**
231
+ * Sorts options based on the specified mode
232
+ */
233
+ function sortOptions(options, config) {
234
+ const { mode, customComparator, getLabel, recentlyUsedIds } = config;
235
+ if (mode === 'none') {
236
+ return options;
237
+ }
238
+ const sorted = [...options];
239
+ switch (mode) {
240
+ case 'alphabetical-asc':
241
+ sorted.sort((a, b) => {
242
+ const labelA = getLabel ? getLabel(a) : (a.label || String(a.value));
243
+ const labelB = getLabel ? getLabel(b) : (b.label || String(b.value));
244
+ return labelA.localeCompare(labelB);
245
+ });
246
+ break;
247
+ case 'alphabetical-desc':
248
+ sorted.sort((a, b) => {
249
+ const labelA = getLabel ? getLabel(a) : (a.label || String(a.value));
250
+ const labelB = getLabel ? getLabel(b) : (b.label || String(b.value));
251
+ return labelB.localeCompare(labelA);
252
+ });
253
+ break;
254
+ case 'recently-used':
255
+ if (recentlyUsedIds && recentlyUsedIds.size > 0) {
256
+ sorted.sort((a, b) => {
257
+ const aRecent = recentlyUsedIds.has(a.id);
258
+ const bRecent = recentlyUsedIds.has(b.id);
259
+ if (aRecent && !bRecent)
260
+ return -1;
261
+ if (!aRecent && bRecent)
262
+ return 1;
263
+ // If both or neither are recent, maintain original order
264
+ return 0;
265
+ });
266
+ }
267
+ break;
268
+ case 'custom':
269
+ if (customComparator) {
270
+ sorted.sort(customComparator);
271
+ }
272
+ break;
273
+ }
274
+ return sorted;
275
+ }
276
+
277
+ /**
278
+ * Dark mode detection and management provider
279
+ * Uses CSS media queries to detect system dark mode preference
280
+ */
281
+ class DarkModeProvider {
282
+ platformId = inject(PLATFORM_ID);
283
+ isBrowser = isPlatformBrowser(this.platformId);
284
+ // User preference (auto follows system, or manual light/dark)
285
+ preferredScheme = signal('auto', ...(ngDevMode ? [{ debugName: "preferredScheme" }] : []));
286
+ // System dark mode detection
287
+ systemPrefersDark = signal(false, ...(ngDevMode ? [{ debugName: "systemPrefersDark" }] : []));
288
+ // Computed: final resolved dark mode state
289
+ isDarkMode = computed(() => {
290
+ const preferred = this.preferredScheme();
291
+ if (preferred === 'auto') {
292
+ return this.systemPrefersDark();
293
+ }
294
+ return preferred === 'dark';
295
+ }, ...(ngDevMode ? [{ debugName: "isDarkMode" }] : []));
296
+ constructor() {
297
+ if (this.isBrowser) {
298
+ this.initDarkModeDetection();
299
+ }
300
+ }
301
+ /**
302
+ * Initialize system dark mode detection
303
+ */
304
+ initDarkModeDetection() {
305
+ // Check initial system preference
306
+ const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
307
+ this.systemPrefersDark.set(mediaQuery.matches);
308
+ // Listen for system theme changes
309
+ mediaQuery.addEventListener('change', (e) => {
310
+ this.systemPrefersDark.set(e.matches);
311
+ });
312
+ }
313
+ /**
314
+ * Set user's color scheme preference
315
+ */
316
+ setColorScheme(scheme) {
317
+ this.preferredScheme.set(scheme);
318
+ }
319
+ /**
320
+ * Get current color scheme preference
321
+ */
322
+ getColorScheme() {
323
+ return this.preferredScheme();
324
+ }
325
+ /**
326
+ * Toggle between light and dark mode
327
+ */
328
+ toggleDarkMode() {
329
+ const current = this.isDarkMode();
330
+ this.preferredScheme.set(current ? 'light' : 'dark');
331
+ }
332
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: DarkModeProvider, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
333
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: DarkModeProvider, providedIn: 'root' });
334
+ }
335
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: DarkModeProvider, decorators: [{
336
+ type: Injectable,
337
+ args: [{
338
+ providedIn: 'root'
339
+ }]
340
+ }], ctorParameters: () => [] });
341
+
124
342
  class PerfectSelectComponent {
125
343
  sanitizer;
344
+ darkModeProvider;
126
345
  // Core Props
127
346
  options = [];
128
347
  placeholder = 'Select...';
@@ -229,6 +448,35 @@ class PerfectSelectComponent {
229
448
  collapsibleTags = false;
230
449
  showAllTagsText = 'Show all';
231
450
  showLessTagsText = 'Show less';
451
+ // v2.3.0 Features - Fuzzy Search
452
+ enableFuzzySearch = false;
453
+ fuzzySearchThreshold = 0;
454
+ fuzzySearchCaseSensitive = false;
455
+ // v2.3.0 Features - Dark Mode
456
+ enableAutoThemeDetection = false;
457
+ colorScheme = 'auto';
458
+ darkModeTheme = 'dark';
459
+ lightModeTheme = 'blue';
460
+ // v2.3.0 Features - Loading Skeleton
461
+ enableLoadingSkeleton = true;
462
+ skeletonItemCount = 5;
463
+ skeletonItemHeight = 40;
464
+ skeletonAnimationDelay = 800;
465
+ // v2.3.0 Features - Compact Mode
466
+ compactMode = false;
467
+ // v2.3.0 Features - Option Checkboxes
468
+ showOptionCheckboxes = false;
469
+ checkboxPosition = 'left';
470
+ checkboxStyle = 'default';
471
+ // v2.3.0 Features - Bulk Actions
472
+ bulkActions = [];
473
+ enableBulkActions = false;
474
+ bulkActionsPosition = 'above';
475
+ bulkActionsLabel = 'Actions:';
476
+ // v2.3.0 Features - Option Sorting
477
+ sortMode = 'none';
478
+ customSortComparator = null;
479
+ recentlyUsedLimit = 10;
232
480
  // Behavior
233
481
  name = 'angular-perfect-select';
234
482
  id = 'angular-perfect-select';
@@ -258,6 +506,8 @@ class PerfectSelectComponent {
258
506
  // v2.1.0 Events
259
507
  reorder = new EventEmitter();
260
508
  pin = new EventEmitter();
509
+ // v2.3.0 Events
510
+ bulkActionSelected = new EventEmitter();
261
511
  // ViewChildren
262
512
  selectContainerRef;
263
513
  searchInputRef;
@@ -266,6 +516,7 @@ class PerfectSelectComponent {
266
516
  // ContentChildren - Custom Templates
267
517
  optionTemplate;
268
518
  selectedOptionTemplate;
519
+ tagTemplate;
269
520
  // Signals for reactive state
270
521
  isOpen = signal(false, ...(ngDevMode ? [{ debugName: "isOpen" }] : []));
271
522
  searchTerm = signal('', ...(ngDevMode ? [{ debugName: "searchTerm" }] : []));
@@ -292,6 +543,9 @@ class PerfectSelectComponent {
292
543
  pinnedOptionsStorageKey = '';
293
544
  // v2.2.0 Signals
294
545
  tagsExpanded = signal(false, ...(ngDevMode ? [{ debugName: "tagsExpanded" }] : []));
546
+ // v2.3.0 Signals
547
+ isDarkMode = signal(false, ...(ngDevMode ? [{ debugName: "isDarkMode" }] : []));
548
+ recentlyUsedIds = signal(new Set(), ...(ngDevMode ? [{ debugName: "recentlyUsedIds" }] : []));
295
549
  // Computed signals
296
550
  currentTheme = computed(() => THEMES[this.theme] || THEMES.blue, ...(ngDevMode ? [{ debugName: "currentTheme" }] : []));
297
551
  filteredOptions = computed(() => {
@@ -302,13 +556,34 @@ class PerfectSelectComponent {
302
556
  if (this.minSearchLength > 0 && term.length < this.minSearchLength) {
303
557
  return [];
304
558
  }
305
- let filtered = !term ? opts : opts.filter(option => {
306
- if (this.filterOption) {
307
- return this.filterOption(option, term);
308
- }
309
- const label = this.getOptionLabel(option);
310
- return label.toLowerCase().includes(term.toLowerCase());
311
- });
559
+ let filtered;
560
+ // v2.3.0: Fuzzy search if enabled
561
+ if (this.enableFuzzySearch && term) {
562
+ filtered = sortByFuzzyScore(opts, term, this.getOptionLabel, {
563
+ caseSensitive: this.fuzzySearchCaseSensitive,
564
+ threshold: this.fuzzySearchThreshold
565
+ });
566
+ }
567
+ else {
568
+ // Standard filtering
569
+ filtered = !term ? opts : opts.filter(option => {
570
+ if (this.filterOption) {
571
+ return this.filterOption(option, term);
572
+ }
573
+ const label = this.getOptionLabel(option);
574
+ return label.toLowerCase().includes(term.toLowerCase());
575
+ });
576
+ }
577
+ // v2.3.0: Apply sorting if configured
578
+ if (this.sortMode !== 'none' && !term) {
579
+ const sortConfig = {
580
+ mode: this.sortMode,
581
+ customComparator: this.customSortComparator || undefined,
582
+ getLabel: this.getOptionLabel,
583
+ recentlyUsedIds: this.recentlyUsedIds()
584
+ };
585
+ filtered = sortOptions(filtered, sortConfig);
586
+ }
312
587
  // v2.1.0: Sort pinned options to the top
313
588
  if (this.enablePinning && pinned.length > 0) {
314
589
  const pinnedIds = new Set(pinned.map(p => p.id));
@@ -359,6 +634,18 @@ class PerfectSelectComponent {
359
634
  const visible = this.visibleTags();
360
635
  return selected.length - visible.length;
361
636
  }, ...(ngDevMode ? [{ debugName: "hiddenTagsCount" }] : []));
637
+ // v2.3.0 Computed signals
638
+ resolvedTheme = computed(() => {
639
+ if (this.enableAutoThemeDetection) {
640
+ return this.isDarkMode() ? this.darkModeTheme : this.lightModeTheme;
641
+ }
642
+ return this.theme;
643
+ }, ...(ngDevMode ? [{ debugName: "resolvedTheme" }] : []));
644
+ hasBulkActions = computed(() => {
645
+ return this.enableBulkActions &&
646
+ this.bulkActions.length > 0 &&
647
+ this.selectedOptions().length > 0;
648
+ }, ...(ngDevMode ? [{ debugName: "hasBulkActions" }] : []));
362
649
  groupedOptions = computed(() => {
363
650
  if (!this.isGrouped || !this.groupBy) {
364
651
  return null;
@@ -454,8 +741,9 @@ class PerfectSelectComponent {
454
741
  // ControlValueAccessor
455
742
  onChange = () => { };
456
743
  onTouched = () => { };
457
- constructor(sanitizer) {
744
+ constructor(sanitizer, darkModeProvider) {
458
745
  this.sanitizer = sanitizer;
746
+ this.darkModeProvider = darkModeProvider;
459
747
  }
460
748
  ngOnChanges(changes) {
461
749
  // Update internal options when the options input changes
@@ -502,6 +790,13 @@ class PerfectSelectComponent {
502
790
  this.pinnedOptionsStorageKey = `ps-pinned-${this.name || this.id}`;
503
791
  this.loadPinnedOptions();
504
792
  }
793
+ // v2.3.0: Initialize dark mode detection
794
+ if (this.enableAutoThemeDetection) {
795
+ effect(() => {
796
+ const darkMode = this.darkModeProvider.isDarkMode();
797
+ this.isDarkMode.set(darkMode);
798
+ });
799
+ }
505
800
  // Auto-focus if needed
506
801
  if (this.autoFocus) {
507
802
  setTimeout(() => {
@@ -712,6 +1007,10 @@ class PerfectSelectComponent {
712
1007
  if (!exists && this.showRecentSelections) {
713
1008
  this.addToRecentSelections(option);
714
1009
  }
1010
+ // v2.3.0: Track recently used for sorting
1011
+ if (!exists && this.sortMode === 'recently-used') {
1012
+ this.trackRecentlyUsed(option);
1013
+ }
715
1014
  }
716
1015
  else {
717
1016
  this.internalValue.set(optionValue);
@@ -725,6 +1024,10 @@ class PerfectSelectComponent {
725
1024
  if (this.showRecentSelections) {
726
1025
  this.addToRecentSelections(option);
727
1026
  }
1027
+ // v2.3.0: Track recently used for sorting
1028
+ if (this.sortMode === 'recently-used') {
1029
+ this.trackRecentlyUsed(option);
1030
+ }
728
1031
  }
729
1032
  if (this.closeMenuOnSelect) {
730
1033
  this.closeDropdown();
@@ -1144,21 +1447,66 @@ class PerfectSelectComponent {
1144
1447
  getValidationClass() {
1145
1448
  return `validation-${this.validationState}`;
1146
1449
  }
1147
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: PerfectSelectComponent, deps: [{ token: i1.DomSanitizer }], target: i0.ɵɵFactoryTarget.Component });
1148
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.15", type: PerfectSelectComponent, isStandalone: true, selector: "ng-perfect-select", inputs: { options: "options", placeholder: "placeholder", isMulti: "isMulti", multiple: "multiple", isSearchable: "isSearchable", searchable: "searchable", isClearable: "isClearable", clearable: "clearable", isDisabled: "isDisabled", disabled: "disabled", isLoading: "isLoading", loading: "loading", isRtl: "isRtl", closeMenuOnSelect: "closeMenuOnSelect", hideSelectedOptions: "hideSelectedOptions", isCreatable: "isCreatable", allowCreateWhileLoading: "allowCreateWhileLoading", createOptionPosition: "createOptionPosition", formatCreateLabel: "formatCreateLabel", loadOptions: "loadOptions", cacheOptions: "cacheOptions", defaultOptions: "defaultOptions", selectSize: "selectSize", containerSize: "containerSize", theme: "theme", borderRadius: "borderRadius", customStyles: "customStyles", maxHeight: "maxHeight", menuPlacement: "menuPlacement", menuPosition: "menuPosition", getOptionLabel: "getOptionLabel", getOptionValue: "getOptionValue", isOptionDisabled: "isOptionDisabled", filterOption: "filterOption", isGrouped: "isGrouped", groupBy: "groupBy", showSelectAll: "showSelectAll", selectAllText: "selectAllText", deselectAllText: "deselectAllText", showOptionIcons: "showOptionIcons", showOptionBadges: "showOptionBadges", maxOptionsDisplay: "maxOptionsDisplay", optionHeight: "optionHeight", emptyStateText: "emptyStateText", emptySearchText: "emptySearchText", maxSelectedOptions: "maxSelectedOptions", maxSelectedMessage: "maxSelectedMessage", debounceTime: "debounceTime", minSearchLength: "minSearchLength", minSearchMessage: "minSearchMessage", enableVirtualScroll: "enableVirtualScroll", virtualScrollItemSize: "virtualScrollItemSize", virtualScrollMinBufferPx: "virtualScrollMinBufferPx", virtualScrollMaxBufferPx: "virtualScrollMaxBufferPx", validationState: "validationState", validationMessage: "validationMessage", showValidationIcon: "showValidationIcon", showTooltips: "showTooltips", tooltipDelay: "tooltipDelay", getOptionTooltip: "getOptionTooltip", showRecentSelections: "showRecentSelections", recentSelectionsLimit: "recentSelectionsLimit", recentSelectionsLabel: "recentSelectionsLabel", enableRecentSelectionsPersistence: "enableRecentSelectionsPersistence", enableInfiniteScroll: "enableInfiniteScroll", infiniteScrollThreshold: "infiniteScrollThreshold", totalOptionsCount: "totalOptionsCount", enableAdvancedKeyboard: "enableAdvancedKeyboard", typeAheadDelay: "typeAheadDelay", enableCopyPaste: "enableCopyPaste", copyDelimiter: "copyDelimiter", pasteDelimiter: "pasteDelimiter", enableDragDrop: "enableDragDrop", dragDropPlaceholder: "dragDropPlaceholder", dragDropAnimation: "dragDropAnimation", enablePinning: "enablePinning", maxPinnedOptions: "maxPinnedOptions", pinnedOptionsLabel: "pinnedOptionsLabel", persistPinnedOptions: "persistPinnedOptions", enableSearchHighlight: "enableSearchHighlight", searchHighlightColor: "searchHighlightColor", searchHighlightTextColor: "searchHighlightTextColor", maxVisibleTags: "maxVisibleTags", showMoreTagsText: "showMoreTagsText", collapsibleTags: "collapsibleTags", showAllTagsText: "showAllTagsText", showLessTagsText: "showLessTagsText", name: "name", id: "id", autoFocus: "autoFocus", openMenuOnFocus: "openMenuOnFocus", openMenuOnClick: "openMenuOnClick", tabSelectsValue: "tabSelectsValue", backspaceRemovesValue: "backspaceRemovesValue", escapeClearsValue: "escapeClearsValue", noOptionsMessage: "noOptionsMessage", loadingMessage: "loadingMessage" }, outputs: { change: "change", clear: "clear", focus: "focus", blur: "blur", menuOpen: "menuOpen", menuClose: "menuClose", inputChange: "inputChange", createOption: "createOption", optionsLoaded: "optionsLoaded", loadError: "loadError", copy: "copy", paste: "paste", scrollEnd: "scrollEnd", reorder: "reorder", pin: "pin" }, host: { listeners: { "keydown": "handleKeydown($event)", "paste": "handlePaste($event)" } }, providers: [{
1450
+ // v2.3.0 Methods
1451
+ // Track recently used options for sorting
1452
+ trackRecentlyUsed(option) {
1453
+ const ids = this.recentlyUsedIds();
1454
+ const newIds = new Set(ids);
1455
+ newIds.add(option.id);
1456
+ // Maintain limit
1457
+ if (newIds.size > this.recentlyUsedLimit) {
1458
+ const idsArray = Array.from(newIds);
1459
+ const limitedIds = new Set(idsArray.slice(-this.recentlyUsedLimit));
1460
+ this.recentlyUsedIds.set(limitedIds);
1461
+ }
1462
+ else {
1463
+ this.recentlyUsedIds.set(newIds);
1464
+ }
1465
+ }
1466
+ // Check if option is selected (for checkbox mode)
1467
+ isOptionSelected(option) {
1468
+ const selected = this.selectedOptions();
1469
+ const optionValue = this.getOptionValue(option);
1470
+ return selected.some(s => this.getOptionValue(s) === optionValue);
1471
+ }
1472
+ // Execute bulk action
1473
+ executeBulkAction(action) {
1474
+ if (action.disabled || this.selectedOptions().length === 0)
1475
+ return;
1476
+ const selectedOptions = this.selectedOptions();
1477
+ // Execute action callback
1478
+ action.action(selectedOptions);
1479
+ // Emit event
1480
+ this.bulkActionSelected.emit({
1481
+ action,
1482
+ selectedOptions
1483
+ });
1484
+ }
1485
+ // Get skeleton items array for loading state
1486
+ getSkeletonItems() {
1487
+ return Array.from({ length: this.skeletonItemCount }, (_, i) => i);
1488
+ }
1489
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: PerfectSelectComponent, deps: [{ token: i1.DomSanitizer }, { token: DarkModeProvider }], target: i0.ɵɵFactoryTarget.Component });
1490
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.15", type: PerfectSelectComponent, isStandalone: true, selector: "ng-perfect-select", inputs: { options: "options", placeholder: "placeholder", isMulti: "isMulti", multiple: "multiple", isSearchable: "isSearchable", searchable: "searchable", isClearable: "isClearable", clearable: "clearable", isDisabled: "isDisabled", disabled: "disabled", isLoading: "isLoading", loading: "loading", isRtl: "isRtl", closeMenuOnSelect: "closeMenuOnSelect", hideSelectedOptions: "hideSelectedOptions", isCreatable: "isCreatable", allowCreateWhileLoading: "allowCreateWhileLoading", createOptionPosition: "createOptionPosition", formatCreateLabel: "formatCreateLabel", loadOptions: "loadOptions", cacheOptions: "cacheOptions", defaultOptions: "defaultOptions", selectSize: "selectSize", containerSize: "containerSize", theme: "theme", borderRadius: "borderRadius", customStyles: "customStyles", maxHeight: "maxHeight", menuPlacement: "menuPlacement", menuPosition: "menuPosition", getOptionLabel: "getOptionLabel", getOptionValue: "getOptionValue", isOptionDisabled: "isOptionDisabled", filterOption: "filterOption", isGrouped: "isGrouped", groupBy: "groupBy", showSelectAll: "showSelectAll", selectAllText: "selectAllText", deselectAllText: "deselectAllText", showOptionIcons: "showOptionIcons", showOptionBadges: "showOptionBadges", maxOptionsDisplay: "maxOptionsDisplay", optionHeight: "optionHeight", emptyStateText: "emptyStateText", emptySearchText: "emptySearchText", maxSelectedOptions: "maxSelectedOptions", maxSelectedMessage: "maxSelectedMessage", debounceTime: "debounceTime", minSearchLength: "minSearchLength", minSearchMessage: "minSearchMessage", enableVirtualScroll: "enableVirtualScroll", virtualScrollItemSize: "virtualScrollItemSize", virtualScrollMinBufferPx: "virtualScrollMinBufferPx", virtualScrollMaxBufferPx: "virtualScrollMaxBufferPx", validationState: "validationState", validationMessage: "validationMessage", showValidationIcon: "showValidationIcon", showTooltips: "showTooltips", tooltipDelay: "tooltipDelay", getOptionTooltip: "getOptionTooltip", showRecentSelections: "showRecentSelections", recentSelectionsLimit: "recentSelectionsLimit", recentSelectionsLabel: "recentSelectionsLabel", enableRecentSelectionsPersistence: "enableRecentSelectionsPersistence", enableInfiniteScroll: "enableInfiniteScroll", infiniteScrollThreshold: "infiniteScrollThreshold", totalOptionsCount: "totalOptionsCount", enableAdvancedKeyboard: "enableAdvancedKeyboard", typeAheadDelay: "typeAheadDelay", enableCopyPaste: "enableCopyPaste", copyDelimiter: "copyDelimiter", pasteDelimiter: "pasteDelimiter", enableDragDrop: "enableDragDrop", dragDropPlaceholder: "dragDropPlaceholder", dragDropAnimation: "dragDropAnimation", enablePinning: "enablePinning", maxPinnedOptions: "maxPinnedOptions", pinnedOptionsLabel: "pinnedOptionsLabel", persistPinnedOptions: "persistPinnedOptions", enableSearchHighlight: "enableSearchHighlight", searchHighlightColor: "searchHighlightColor", searchHighlightTextColor: "searchHighlightTextColor", maxVisibleTags: "maxVisibleTags", showMoreTagsText: "showMoreTagsText", collapsibleTags: "collapsibleTags", showAllTagsText: "showAllTagsText", showLessTagsText: "showLessTagsText", enableFuzzySearch: "enableFuzzySearch", fuzzySearchThreshold: "fuzzySearchThreshold", fuzzySearchCaseSensitive: "fuzzySearchCaseSensitive", enableAutoThemeDetection: "enableAutoThemeDetection", colorScheme: "colorScheme", darkModeTheme: "darkModeTheme", lightModeTheme: "lightModeTheme", enableLoadingSkeleton: "enableLoadingSkeleton", skeletonItemCount: "skeletonItemCount", skeletonItemHeight: "skeletonItemHeight", skeletonAnimationDelay: "skeletonAnimationDelay", compactMode: "compactMode", showOptionCheckboxes: "showOptionCheckboxes", checkboxPosition: "checkboxPosition", checkboxStyle: "checkboxStyle", bulkActions: "bulkActions", enableBulkActions: "enableBulkActions", bulkActionsPosition: "bulkActionsPosition", bulkActionsLabel: "bulkActionsLabel", sortMode: "sortMode", customSortComparator: "customSortComparator", recentlyUsedLimit: "recentlyUsedLimit", name: "name", id: "id", autoFocus: "autoFocus", openMenuOnFocus: "openMenuOnFocus", openMenuOnClick: "openMenuOnClick", tabSelectsValue: "tabSelectsValue", backspaceRemovesValue: "backspaceRemovesValue", escapeClearsValue: "escapeClearsValue", noOptionsMessage: "noOptionsMessage", loadingMessage: "loadingMessage" }, outputs: { change: "change", clear: "clear", focus: "focus", blur: "blur", menuOpen: "menuOpen", menuClose: "menuClose", inputChange: "inputChange", createOption: "createOption", optionsLoaded: "optionsLoaded", loadError: "loadError", copy: "copy", paste: "paste", scrollEnd: "scrollEnd", reorder: "reorder", pin: "pin", bulkActionSelected: "bulkActionSelected" }, host: { listeners: { "keydown": "handleKeydown($event)", "paste": "handlePaste($event)" } }, providers: [{
1149
1491
  provide: NG_VALUE_ACCESSOR,
1150
1492
  useExisting: forwardRef(() => PerfectSelectComponent),
1151
1493
  multi: true
1152
- }], queries: [{ propertyName: "optionTemplate", first: true, predicate: ["optionTemplate"], descendants: true, read: TemplateRef }, { propertyName: "selectedOptionTemplate", first: true, predicate: ["selectedOptionTemplate"], descendants: true, read: TemplateRef }], viewQueries: [{ propertyName: "selectContainerRef", first: true, predicate: ["selectContainer"], descendants: true }, { propertyName: "searchInputRef", first: true, predicate: ["searchInput"], descendants: true }, { propertyName: "menuElementRef", first: true, predicate: ["menuRef"], descendants: true }, { propertyName: "virtualScrollViewport", first: true, predicate: CdkVirtualScrollViewport, descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div\n #selectContainer\n class=\"select-container {{selectSize}} {{containerSize}} theme-{{theme}} {{getValidationClass()}}\"\n [class.disabled]=\"isDisabled\"\n [class.rtl]=\"isRtl\"\n [class.has-validation]=\"validationState !== 'default'\"\n (clickOutside)=\"onClickOutside()\"\n role=\"combobox\"\n tabindex=\"0\"\n [attr.aria-controls]=\"'options-list'\"\n [attr.aria-expanded]=\"isOpen()\"\n [attr.aria-invalid]=\"validationState === 'error'\"\n (focus)=\"openMenuOnFocus && !isOpen() && toggleDropdown(); focus.emit()\"\n [style]=\"customStyles.container || ''\"\n>\n <!-- Main Select Trigger -->\n <div\n class=\"select-trigger\"\n [class.open]=\"isOpen()\"\n [class.focused]=\"isOpen()\"\n (click)=\"openMenuOnClick && toggleDropdown()\"\n [attr.tabindex]=\"isDisabled ? -1 : 0\"\n role=\"button\"\n aria-haspopup=\"listbox\"\n [attr.aria-expanded]=\"isOpen()\"\n [attr.aria-label]=\"placeholder\"\n >\n <!-- Selected Value Display -->\n <div class=\"select-value\">\n <!-- Multi-select Tags -->\n @if (isMulti && selectedOptions().length > 0) {\n <!-- v2.1.0: Drag-drop enabled tags -->\n @if (enableDragDrop) {\n <div class=\"tags\" cdkDropList cdkDropListOrientation=\"horizontal\" (cdkDropListDropped)=\"onTagsReorder($event)\" [class.dragging]=\"isDragging()\">\n @for (option of visibleTags(); track trackByValue($index, option)) {\n <span class=\"tag\" [class.disabled]=\"isDisabled\" cdkDrag (cdkDragStarted)=\"onDragStart()\" (cdkDragEnded)=\"onDragEnd()\" [@tag]>\n <div class=\"drag-handle\" cdkDragHandle>\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <path d=\"M9 3h2v2H9V3zm0 4h2v2H9V7zm0 4h2v2H9v-2zm0 4h2v2H9v-2zm0 4h2v2H9v-2zm4-16h2v2h-2V3zm0 4h2v2h-2V7zm0 4h2v2h-2v-2zm0 4h2v2h-2v-2zm0 4h2v2h-2v-2z\"/>\n </svg>\n </div>\n <span class=\"tag-label\">{{getOptionLabel(option)}}</span>\n @if (!isDisabled) {\n <button\n class=\"tag-remove\"\n (click)=\"removeOption(option, $event)\"\n [attr.aria-label]=\"'Remove ' + getOptionLabel(option)\"\n type=\"button\"\n >\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 20 20\">\n <path d=\"M14.348 14.849c-0.469 0.469-1.229 0.469-1.697 0l-2.651-3.030-2.651 3.029c-0.469 0.469-1.229 0.469-1.697 0-0.469-0.469-0.469-1.229 0-1.697l2.758-3.15-2.759-3.152c-0.469-0.469-0.469-1.228 0-1.697s1.228-0.469 1.697 0l2.652 3.031 2.651-3.031c0.469-0.469 1.228-0.469 1.697 0s0.469 1.229 0 1.697l-2.758 3.152 2.758 3.15c0.469 0.469 0.469 1.229 0 1.698z\"></path>\n </svg>\n </button>\n }\n <div *cdkDragPlaceholder class=\"drag-placeholder\">{{dragDropPlaceholder}}</div>\n </span>\n }\n </div>\n } @else {\n <div class=\"tags\">\n @for (option of visibleTags(); track trackByValue($index, option)) {\n <span class=\"tag\" [class.disabled]=\"isDisabled\" [@tag]>\n <span class=\"tag-label\">{{getOptionLabel(option)}}</span>\n @if (!isDisabled) {\n <button\n class=\"tag-remove\"\n (click)=\"removeOption(option, $event)\"\n [attr.aria-label]=\"'Remove ' + getOptionLabel(option)\"\n type=\"button\"\n >\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 20 20\">\n <path d=\"M14.348 14.849c-0.469 0.469-1.229 0.469-1.697 0l-2.651-3.030-2.651 3.029c-0.469 0.469-1.229 0.469-1.697 0-0.469-0.469-0.469-1.229 0-1.697l2.758-3.15-2.759-3.152c-0.469-0.469-0.469-1.228 0-1.697s1.228-0.469 1.697 0l2.652 3.031 2.651-3.031c0.469-0.469 1.228-0.469 1.697 0s0.469 1.229 0 1.697l-2.758 3.152 2.758 3.15c0.469 0.469 0.469 1.229 0 1.698z\"></path>\n </svg>\n </button>\n }\n </span>\n }\n </div>\n }\n\n <!-- v2.2.0: Tag overflow indicator -->\n @if (hiddenTagsCount() > 0) {\n @if (collapsibleTags) {\n <button\n class=\"tags-toggle\"\n (click)=\"toggleTagsExpanded()\"\n [attr.aria-label]=\"tagsExpanded() ? showLessTagsText : showAllTagsText\"\n type=\"button\"\n >\n {{ tagsExpanded() ? showLessTagsText : showAllTagsText }}\n </button>\n } @else {\n <span class=\"tags-overflow-indicator\">\n {{ getMoreTagsText() }}\n </span>\n }\n }\n } @else {\n <!-- Single Select or Placeholder -->\n <span class=\"placeholder\" [class.has-value]=\"selectedOptions().length > 0\">\n {{displayText()}}\n </span>\n }\n </div>\n\n <!-- Actions (Clear, Loading, Arrow) -->\n <div class=\"select-actions\">\n @if (isLoading || isLoadingAsync()) {\n <div class=\"spinner\"></div>\n }\n @if (isClearable && selectedOptions().length > 0 && !isDisabled && !isLoading && !isLoadingAsync()) {\n <button\n class=\"clear-button\"\n (click)=\"clearSelection($event)\"\n aria-label=\"Clear selection\"\n type=\"button\"\n >\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 20 20\">\n <path d=\"M14.348 14.849c-0.469 0.469-1.229 0.469-1.697 0l-2.651-3.030-2.651 3.029c-0.469 0.469-1.229 0.469-1.697 0-0.469-0.469-0.469-1.229 0-1.697l2.758-3.15-2.759-3.152c-0.469-0.469-0.469-1.228 0-1.697s1.228-0.469 1.697 0l2.652 3.031 2.651-3.031c0.469-0.469 1.228-0.469 1.697 0s0.469 1.229 0 1.697l-2.758 3.152 2.758 3.15c0.469 0.469 0.469 1.229 0 1.698z\"></path>\n </svg>\n </button>\n }\n <span class=\"separator\"></span>\n <span class=\"arrow\" [class.open]=\"isOpen()\">\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 20 20\">\n <path d=\"M4.516 7.548c0.436-0.446 1.043-0.481 1.576 0l3.908 3.747 3.908-3.747c0.533-0.481 1.141-0.446 1.574 0 0.436 0.445 0.408 1.197 0 1.615-0.406 0.418-4.695 4.502-4.695 4.502-0.217 0.223-0.502 0.335-0.787 0.335s-0.57-0.112-0.789-0.335c0 0-4.287-4.084-4.695-4.502s-0.436-1.17 0-1.615z\"></path>\n </svg>\n </span>\n </div>\n </div>\n\n <!-- Dropdown Menu -->\n @if (isOpen()) {\n <div\n #menuRef\n class=\"dropdown {{menuPlacement}}\"\n [class.fixed]=\"menuPosition === 'fixed'\"\n [style.max-height]=\"maxHeight\"\n role=\"listbox\"\n [attr.aria-multiselectable]=\"isMulti\"\n [@dropdown]\n >\n <!-- Search Input -->\n @if (isSearchable) {\n <div class=\"search-container\">\n <input\n #searchInput\n type=\"text\"\n class=\"search-input\"\n placeholder=\"Search...\"\n [value]=\"searchTerm()\"\n (input)=\"onSearchChange($any($event.target).value)\"\n (click)=\"$event.stopPropagation()\"\n aria-label=\"Search options\"\n aria-autocomplete=\"list\"\n />\n </div>\n }\n\n <!-- Options List -->\n <div\n class=\"options-list\"\n id=\"options-list\"\n (scroll)=\"onOptionsScroll($event)\"\n >\n <!-- Max Selection Message -->\n @if (isMaxSelectionReached()) {\n <div class=\"info-message max-selection\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 20 20\" fill=\"currentColor\">\n <path d=\"M10 0C4.477 0 0 4.477 0 10s4.477 10 10 10 10-4.477 10-10S15.523 0 10 0zm1 15H9v-2h2v2zm0-4H9V5h2v6z\"/>\n </svg>\n {{maxSelectedMessage}}\n </div>\n }\n\n <!-- Min Search Length Message -->\n @if (showMinSearchMessage()) {\n <div class=\"info-message min-search\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 20 20\" fill=\"currentColor\">\n <path d=\"M10 0C4.477 0 0 4.477 0 10s4.477 10 10 10 10-4.477 10-10S15.523 0 10 0zm1 15H9v-2h2v2zm0-4H9V5h2v6z\"/>\n </svg>\n {{minSearchMessage}} ({{searchTerm().length}}/{{minSearchLength}})\n </div>\n }\n\n @if (isLoadingAsync()) {\n <div class=\"loading-message\">{{loadingMessage()}}</div>\n } @else if (displayOptions().length === 0 && !showMinSearchMessage()) {\n <div class=\"no-options\">\n {{searchTerm() ? emptySearchText : emptyStateText}}\n </div>\n } @else if (!showMinSearchMessage()) {\n <!-- Select All (Multi-select only) -->\n @if (isMulti && showSelectAll && !searchTerm()) {\n <div class=\"select-all-container\">\n <button\n class=\"select-all-button\"\n (click)=\"allOptionsSelected() ? deselectAll() : selectAll()\"\n type=\"button\"\n >\n <input\n type=\"checkbox\"\n [checked]=\"allOptionsSelected()\"\n [indeterminate]=\"someOptionsSelected()\"\n tabindex=\"-1\"\n aria-hidden=\"true\"\n readonly\n />\n <span class=\"select-all-text\">\n {{allOptionsSelected() ? deselectAllText : selectAllText}}\n </span>\n <span class=\"select-all-count\">\n ({{selectedOptions().length}}/{{getEnabledOptionsCount()}})\n </span>\n </button>\n </div>\n }\n\n <!-- Grouped Options -->\n @if (isGrouped && groupedOptions()) {\n @for (group of groupedOptions() | keyvalue; track trackByGroup($index, [$any(group.key), $any(group.value)])) {\n <div class=\"option-group\">\n <div class=\"option-group-label\">{{group.key}}</div>\n @for (option of $any(group.value); track trackByValue($index, option); let idx = $index) {\n <div\n class=\"option\"\n [class.selected]=\"isSelected(option)\"\n [class.highlighted]=\"idx === highlightedIndex()\"\n [class.disabled]=\"isOptionDisabled(option) || (isMaxSelectionReached() && !isSelected(option))\"\n [class.hidden]=\"hideSelectedOptions && isSelected(option)\"\n [class.create-option]=\"option.__isCreate__\"\n (click)=\"selectOption(option)\"\n (keydown)=\"$event.key === 'Enter' && selectOption(option)\"\n role=\"option\"\n [attr.aria-selected]=\"isSelected(option)\"\n [attr.aria-disabled]=\"isOptionDisabled(option) || (isMaxSelectionReached() && !isSelected(option))\"\n tabindex=\"-1\"\n >\n <!-- Checkbox for multi-select -->\n @if (isMulti) {\n <input\n type=\"checkbox\"\n [checked]=\"isSelected(option)\"\n tabindex=\"-1\"\n aria-hidden=\"true\"\n readonly\n />\n }\n\n <!-- Option Icon -->\n @if (showOptionIcons && option.icon) {\n <div class=\"option-icon\">\n @if (option.icon.includes('<')) {\n <div [innerHTML]=\"sanitizeHtml(option.icon)\"></div>\n } @else {\n <img [src]=\"option.icon\" [alt]=\"getOptionLabel(option)\" />\n }\n </div>\n }\n\n <!-- Option Content -->\n <div class=\"option-content\">\n <div class=\"option-label\" [innerHTML]=\"highlightSearchTerm(getOptionLabel(option))\"></div>\n @if (option.description) {\n <div class=\"option-description\">{{option.description}}</div>\n }\n </div>\n\n <!-- Option Badge -->\n @if (showOptionBadges && option.badge) {\n <span\n class=\"option-badge\"\n [style.background-color]=\"option.badgeColor || currentTheme().primary\"\n >\n {{option.badge}}\n </span>\n }\n\n <!-- v2.1.0: Pin button -->\n @if (enablePinning) {\n <button\n class=\"pin-button\"\n [class.pinned]=\"isPinned(option)\"\n (click)=\"togglePin(option, $event)\"\n [attr.aria-label]=\"isPinned(option) ? 'Unpin option' : 'Pin option'\"\n type=\"button\"\n >\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n @if (isPinned(option)) {\n <path d=\"M16,12V4H17V2H7V4H8V12L6,14V16H11.2V22H12.8V16H18V14L16,12Z\"/>\n } @else {\n <path d=\"M16,12V4H17V2H7V4H8V12L6,14V16H11.2V22H12.8V16H18V14L16,12M8.8,14L10,12.8V4H14V12.8L15.2,14H8.8Z\"/>\n }\n </svg>\n </button>\n }\n\n <!-- Check Icon for selected -->\n @if (!isMulti && isSelected(option)) {\n <svg class=\"check-icon\" width=\"16\" height=\"16\" viewBox=\"0 0 20 20\">\n <path d=\"M7.629,14.566c0.125,0.125,0.291,0.188,0.456,0.188c0.164,0,0.329-0.062,0.456-0.188l8.219-8.221c0.252-0.252,0.252-0.659,0-0.911c-0.252-0.252-0.659-0.252-0.911,0l-7.764,7.763L4.152,9.267c-0.252-0.251-0.66-0.251-0.911,0c-0.252,0.252-0.252,0.66,0,0.911L7.629,14.566z\"></path>\n </svg>\n }\n </div>\n }\n </div>\n }\n } @else {\n <!-- v1.2.0: Recent Selections Header -->\n @if (showRecentSelections && !searchTerm() && recentSelections().length > 0) {\n <div class=\"section-header\">{{recentSelectionsLabel}}</div>\n }\n\n <!-- Regular (Ungrouped) Options - Virtual Scroll or Standard -->\n @if (enableVirtualScroll) {\n <!-- Virtual Scroll Mode -->\n <cdk-virtual-scroll-viewport\n [itemSize]=\"virtualScrollItemSize\"\n [minBufferPx]=\"virtualScrollMinBufferPx\"\n [maxBufferPx]=\"virtualScrollMaxBufferPx\"\n [style.height.px]=\"maxHeight\"\n class=\"virtual-scroll-viewport\"\n >\n @for (option of (showRecentSelections ? displayOptionsWithRecent() : displayOptions()); track trackByValue($index, option); let idx = $index) {\n <div\n class=\"option\"\n [class.selected]=\"isSelected(option)\"\n [class.highlighted]=\"idx === highlightedIndex()\"\n [class.disabled]=\"isOptionDisabled(option) || (isMaxSelectionReached() && !isSelected(option))\"\n [class.hidden]=\"hideSelectedOptions && isSelected(option)\"\n [class.create-option]=\"option.__isCreate__\"\n [class.recent-option]=\"option.__isRecent__\"\n (click)=\"selectOption(option)\"\n (keydown)=\"$event.key === 'Enter' && selectOption(option)\"\n (mouseenter)=\"showTooltip(option, idx)\"\n (mouseleave)=\"hideTooltip()\"\n role=\"option\"\n [attr.aria-selected]=\"isSelected(option)\"\n [attr.aria-disabled]=\"isOptionDisabled(option) || (isMaxSelectionReached() && !isSelected(option))\"\n [attr.title]=\"showTooltips ? getTooltipContent(option) : null\"\n tabindex=\"-1\"\n >\n <!-- Custom Template or Default Rendering -->\n @if (optionTemplate) {\n <ng-container\n *ngTemplateOutlet=\"optionTemplate; context: { $implicit: option, index: idx, selected: isSelected(option) }\"\n ></ng-container>\n } @else {\n <!-- Default Option Content (same as before) -->\n @if (isMulti) {\n <input\n type=\"checkbox\"\n [checked]=\"isSelected(option)\"\n tabindex=\"-1\"\n aria-hidden=\"true\"\n readonly\n />\n }\n\n @if (showOptionIcons && option.icon) {\n <div class=\"option-icon\">\n @if (option.icon.includes('<')) {\n <div [innerHTML]=\"sanitizeHtml(option.icon)\"></div>\n } @else {\n <img [src]=\"option.icon\" [alt]=\"getOptionLabel(option)\" />\n }\n </div>\n }\n\n <div class=\"option-content\">\n <div class=\"option-label\" [innerHTML]=\"highlightSearchTerm(getOptionLabel(option))\"></div>\n @if (option.description) {\n <div class=\"option-description\">{{option.description}}</div>\n }\n </div>\n\n @if (showOptionBadges && option.badge) {\n <span\n class=\"option-badge\"\n [style.background-color]=\"option.badgeColor || currentTheme().primary\"\n >\n {{option.badge}}\n </span>\n }\n\n <!-- v2.1.0: Pin button -->\n @if (enablePinning) {\n <button\n class=\"pin-button\"\n [class.pinned]=\"isPinned(option)\"\n (click)=\"togglePin(option, $event)\"\n [attr.aria-label]=\"isPinned(option) ? 'Unpin option' : 'Pin option'\"\n type=\"button\"\n >\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n @if (isPinned(option)) {\n <path d=\"M16,12V4H17V2H7V4H8V12L6,14V16H11.2V22H12.8V16H18V14L16,12Z\"/>\n } @else {\n <path d=\"M16,12V4H17V2H7V4H8V12L6,14V16H11.2V22H12.8V16H18V14L16,12M8.8,14L10,12.8V4H14V12.8L15.2,14H8.8Z\"/>\n }\n </svg>\n </button>\n }\n\n @if (!isMulti && isSelected(option)) {\n <svg class=\"check-icon\" width=\"16\" height=\"16\" viewBox=\"0 0 20 20\">\n <path d=\"M7.629,14.566c0.125,0.125,0.291,0.188,0.456,0.188c0.164,0,0.329-0.062,0.456-0.188l8.219-8.221c0.252-0.252,0.252-0.659,0-0.911c-0.252-0.252-0.659-0.252-0.911,0l-7.764,7.763L4.152,9.267c-0.252-0.251-0.66-0.251-0.911,0c-0.252,0.252-0.252,0.66,0,0.911L7.629,14.566z\"></path>\n </svg>\n }\n }\n </div>\n }\n </cdk-virtual-scroll-viewport>\n } @else {\n <!-- Standard Rendering (Non-Virtual Scroll) -->\n @for (option of (showRecentSelections ? displayOptionsWithRecent() : displayOptions()); track trackByValue($index, option); let idx = $index) {\n <div\n class=\"option\"\n [class.selected]=\"isSelected(option)\"\n [class.highlighted]=\"idx === highlightedIndex()\"\n [class.disabled]=\"isOptionDisabled(option) || (isMaxSelectionReached() && !isSelected(option))\"\n [class.hidden]=\"hideSelectedOptions && isSelected(option)\"\n [class.create-option]=\"option.__isCreate__\"\n [class.recent-option]=\"option.__isRecent__\"\n (click)=\"selectOption(option)\"\n (keydown)=\"$event.key === 'Enter' && selectOption(option)\"\n (mouseenter)=\"showTooltip(option, idx)\"\n (mouseleave)=\"hideTooltip()\"\n role=\"option\"\n [attr.aria-selected]=\"isSelected(option)\"\n [attr.aria-disabled]=\"isOptionDisabled(option) || (isMaxSelectionReached() && !isSelected(option))\"\n [attr.title]=\"showTooltips ? getTooltipContent(option) : null\"\n tabindex=\"-1\"\n >\n <!-- Custom Template or Default Rendering -->\n @if (optionTemplate) {\n <ng-container\n *ngTemplateOutlet=\"optionTemplate; context: { $implicit: option, index: idx, selected: isSelected(option) }\"\n ></ng-container>\n } @else {\n <!-- Default Option Content -->\n @if (isMulti) {\n <input\n type=\"checkbox\"\n [checked]=\"isSelected(option)\"\n tabindex=\"-1\"\n aria-hidden=\"true\"\n readonly\n />\n }\n\n @if (showOptionIcons && option.icon) {\n <div class=\"option-icon\">\n @if (option.icon.includes('<')) {\n <div [innerHTML]=\"sanitizeHtml(option.icon)\"></div>\n } @else {\n <img [src]=\"option.icon\" [alt]=\"getOptionLabel(option)\" />\n }\n </div>\n }\n\n <div class=\"option-content\">\n <div class=\"option-label\" [innerHTML]=\"highlightSearchTerm(getOptionLabel(option))\"></div>\n @if (option.description) {\n <div class=\"option-description\">{{option.description}}</div>\n }\n </div>\n\n @if (showOptionBadges && option.badge) {\n <span\n class=\"option-badge\"\n [style.background-color]=\"option.badgeColor || currentTheme().primary\"\n >\n {{option.badge}}\n </span>\n }\n\n <!-- v2.1.0: Pin button -->\n @if (enablePinning) {\n <button\n class=\"pin-button\"\n [class.pinned]=\"isPinned(option)\"\n (click)=\"togglePin(option, $event)\"\n [attr.aria-label]=\"isPinned(option) ? 'Unpin option' : 'Pin option'\"\n type=\"button\"\n >\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n @if (isPinned(option)) {\n <path d=\"M16,12V4H17V2H7V4H8V12L6,14V16H11.2V22H12.8V16H18V14L16,12Z\"/>\n } @else {\n <path d=\"M16,12V4H17V2H7V4H8V12L6,14V16H11.2V22H12.8V16H18V14L16,12M8.8,14L10,12.8V4H14V12.8L15.2,14H8.8Z\"/>\n }\n </svg>\n </button>\n }\n\n @if (!isMulti && isSelected(option)) {\n <svg class=\"check-icon\" width=\"16\" height=\"16\" viewBox=\"0 0 20 20\">\n <path d=\"M7.629,14.566c0.125,0.125,0.291,0.188,0.456,0.188c0.164,0,0.329-0.062,0.456-0.188l8.219-8.221c0.252-0.252,0.252-0.659,0-0.911c-0.252-0.252-0.659-0.252-0.911,0l-7.764,7.763L4.152,9.267c-0.252-0.251-0.66-0.251-0.911,0c-0.252,0.252-0.252,0.66,0,0.911L7.629,14.566z\"></path>\n </svg>\n }\n }\n </div>\n }\n }\n }\n }\n </div>\n </div>\n }\n\n <!-- v1.2.0: Validation Message -->\n @if (validationMessage && validationState !== 'default') {\n <div class=\"validation-message {{getValidationClass()}}\">\n @if (showValidationIcon) {\n <span class=\"validation-icon\">\n @if (validationState === 'error') {\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 20 20\" fill=\"currentColor\">\n <path d=\"M10 0C4.477 0 0 4.477 0 10s4.477 10 10 10 10-4.477 10-10S15.523 0 10 0zm1 15H9v-2h2v2zm0-4H9V5h2v6z\"/>\n </svg>\n } @else if (validationState === 'warning') {\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 20 20\" fill=\"currentColor\">\n <path d=\"M10 0C4.477 0 0 4.477 0 10s4.477 10 10 10 10-4.477 10-10S15.523 0 10 0zm1 15H9v-2h2v2zm0-4H9V5h2v6z\"/>\n </svg>\n } @else if (validationState === 'success') {\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 20 20\" fill=\"currentColor\">\n <path d=\"M10 0C4.477 0 0 4.477 0 10s4.477 10 10 10 10-4.477 10-10S15.523 0 10 0zm-1.707 13.707l-3.5-3.5 1.414-1.414L9 11.586l5.293-5.293 1.414 1.414-6.5 6.5z\"/>\n </svg>\n } @else if (validationState === 'info') {\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 20 20\" fill=\"currentColor\">\n <path d=\"M10 0C4.477 0 0 4.477 0 10s4.477 10 10 10 10-4.477 10-10S15.523 0 10 0zm1 15H9v-6h2v6zm0-8H9V5h2v2z\"/>\n </svg>\n }\n </span>\n }\n <span class=\"validation-text\">{{validationMessage}}</span>\n </div>\n }\n</div>\n\n<!-- Hidden native select for form compatibility -->\n@if (isMulti) {\n <select [name]=\"name\" [id]=\"id\" multiple style=\"display: none;\" [attr.aria-hidden]=\"true\">\n @for (option of selectedOptions(); track trackByValue($index, option)) {\n <option [value]=\"getOptionValue(option)\" selected>{{getOptionLabel(option)}}</option>\n }\n </select>\n} @else {\n @if (selectedOptions().length > 0) {\n <select [name]=\"name\" [id]=\"id\" style=\"display: none;\" [attr.aria-hidden]=\"true\">\n <option [value]=\"getOptionValue(selectedOptions()[0])\" selected>{{getOptionLabel(selectedOptions()[0])}}</option>\n </select>\n }\n}\n", styles: [":host{display:block;width:100%}.select-container{position:relative;width:100%;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif}.smaller{font-size:11px}.small{font-size:13px}.medium{font-size:14px}.large{font-size:16px}.larger{font-size:18px}.xs .select-trigger{min-height:28px;padding:4px 8px}.sm .select-trigger{min-height:32px;padding:6px 10px}.md .select-trigger{min-height:40px;padding:8px 12px}.lg .select-trigger{min-height:48px;padding:10px 14px}.xl .select-trigger{min-height:56px;padding:12px 16px}.select-container.disabled{opacity:.6;cursor:not-allowed}.select-container.rtl{direction:rtl}.select-trigger{display:flex;align-items:center;justify-content:space-between;padding:8px 12px;background:#fff;border:1.5px solid #D1D5DB;border-radius:10px;cursor:pointer;transition:all .2s cubic-bezier(.4,0,.2,1);min-height:38px;gap:8px;box-shadow:0 1px 2px #0000000d}.select-trigger:hover:not(.disabled){border-color:#9ca3af;box-shadow:0 2px 4px #00000014}.select-trigger:focus,.select-trigger.focused{outline:none;border-color:#2684ff;box-shadow:0 0 0 3px #2684ff1a,0 1px 2px #0000000d}.select-trigger.open{border-color:#2684ff;box-shadow:0 0 0 3px #2684ff1a,0 1px 2px #0000000d}.select-value{flex:1;display:flex;align-items:center;min-width:0;gap:4px}.placeholder{color:#999;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.placeholder.has-value{color:#333}.tags{display:flex;flex-wrap:wrap;gap:4px;flex:1}.tag{display:inline-flex;align-items:center;gap:4px;padding:4px 8px;background:#e6f2ff;color:#0052cc;border-radius:6px;font-size:.875em;font-weight:500;white-space:nowrap;border:1px solid #CCE0FF;box-shadow:0 1px 2px #0000000d}.tag.disabled{background:#f0f0f0;color:#666;border-color:#d9d9d9}.tag-label{line-height:1.2}.tag-remove{background:none;border:none;color:inherit;cursor:pointer;padding:0;display:flex;align-items:center;justify-content:center;border-radius:2px;transition:all .15s}.tag-remove:hover{background:#0052cc26}.tag-remove svg{fill:currentColor}.tags.dragging .tag{cursor:move}.drag-handle{display:flex;align-items:center;justify-content:center;cursor:move;padding:0;margin-left:-4px;opacity:.5;transition:opacity .15s}.drag-handle svg{fill:currentColor}.drag-handle:hover{opacity:.8}.cdk-drag-preview{box-shadow:0 5px 15px #0003;opacity:.8}.cdk-drag-animating{transition:transform .25s cubic-bezier(0,0,.2,1)}.drag-placeholder{background:#0052cc1a;border:2px dashed var(--ps-primary, #0052cc);border-radius:6px;padding:4px 8px;min-height:28px;display:flex;align-items:center;justify-content:center;color:var(--ps-primary, #0052cc);font-size:.75em;opacity:.6}.cdk-drop-list-dragging .cdk-drag{transition:transform .25s cubic-bezier(0,0,.2,1)}.tags-toggle{background:none;border:none;color:var(--ps-primary, #0052cc);cursor:pointer;font-size:.875em;font-weight:500;padding:4px 8px;border-radius:4px;transition:all .15s;white-space:nowrap}.tags-toggle:hover{background:#0052cc1a;text-decoration:underline}.tags-toggle:focus{outline:2px solid var(--ps-primary, #0052cc);outline-offset:2px}.tags-overflow-indicator{display:inline-flex;align-items:center;padding:4px 8px;background:#f3f4f6;color:#6b7280;border-radius:6px;font-size:.875em;font-weight:500;white-space:nowrap;border:1px solid #d1d5db}.option-label mark{background-color:#ffeb3b;color:#000;padding:0 2px;border-radius:2px;font-weight:600}.select-actions{display:flex;align-items:center;gap:4px;flex-shrink:0}.clear-button{background:none;border:none;color:#999;cursor:pointer;padding:4px;display:flex;align-items:center;justify-content:center;border-radius:3px;transition:all .15s}.clear-button:hover{color:#333;background:#f0f0f0}.clear-button svg{fill:currentColor}.separator{width:1px;height:24px;background:#ccc;align-self:stretch}.arrow{color:#999;transition:transform .2s cubic-bezier(.4,0,.2,1);display:flex;align-items:center;padding:4px}.arrow svg{fill:currentColor}.arrow.open{transform:rotate(180deg)}.spinner{width:16px;height:16px;border:2px solid #f3f3f3;border-top:2px solid #2684FF;border-radius:50%;animation:spin .6s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}.dropdown{position:absolute;top:calc(100% + 8px);left:0;right:0;background:#fffffffa;border:1px solid #E5E7EB;border-radius:12px;box-shadow:0 20px 25px -5px #0000001a,0 10px 10px -5px #0000000a,0 0 0 1px #0000000d;z-index:1000;overflow:hidden;backdrop-filter:blur(12px) saturate(180%);-webkit-backdrop-filter:blur(12px) saturate(180%)}.dropdown.fixed{position:fixed}.dropdown.top{top:auto;bottom:calc(100% + 4px)}.search-container{padding:8px;border-bottom:1px solid #f0f0f0;background:#fff}.search-input{width:100%;padding:6px 8px;border:1px solid #cccccc;border-radius:3px;font-size:inherit;outline:none;transition:border-color .15s}.search-input:focus{border-color:#2684ff;box-shadow:0 0 0 1px #2684ff}.options-list{max-height:inherit;overflow-y:auto;overflow-x:hidden;padding:4px 0}.options-list::-webkit-scrollbar{width:8px}.options-list::-webkit-scrollbar-track{background:transparent}.options-list::-webkit-scrollbar-thumb{background:#d9d9d9;border-radius:4px}.options-list::-webkit-scrollbar-thumb:hover{background:#b3b3b3}.select-all-container{padding:8px;border-bottom:1px solid #E5E7EB;background:#f9fafb}.select-all-button{width:100%;display:flex;align-items:center;gap:8px;padding:6px 8px;background:#fff;border:1px solid #D1D5DB;border-radius:6px;cursor:pointer;transition:all .15s;font-size:inherit;color:inherit;font-family:inherit}.select-all-button:hover{background:#f3f4f6;border-color:#9ca3af}.select-all-button input[type=checkbox]{cursor:pointer;width:16px;height:16px;margin:0;accent-color:#2684FF}.select-all-text{flex:1;text-align:left;font-weight:500}.select-all-count{color:#6b7280;font-size:.9em}.option-group{margin:4px 0}.option-group-label{padding:8px 12px 4px;font-size:.75em;font-weight:600;color:#6b7280;text-transform:uppercase;letter-spacing:.05em;background:#f9fafb;border-bottom:1px solid #E5E7EB;position:sticky;top:0;z-index:1}.option-group .option{padding-left:20px}.option{display:flex;align-items:center;gap:10px;padding:10px 12px;cursor:pointer;transition:all .15s cubic-bezier(.4,0,.2,1);position:relative;min-height:40px}.option-content{flex:1;display:flex;flex-direction:column;gap:2px;min-width:0}.option-icon{display:flex;align-items:center;justify-content:center;width:32px;height:32px;flex-shrink:0;border-radius:6px;overflow:hidden;background:#f3f4f6}.option-icon img{width:100%;height:100%;object-fit:cover}.option-badge{padding:2px 8px;border-radius:12px;font-size:.75em;font-weight:500;color:#374151;background:#e5e7eb;flex-shrink:0;white-space:nowrap}.option:hover:not(.disabled):not(.create-option){background:#deebff}.option.highlighted{background:#deebff}.option.selected:not(.create-option){background:#e6f2ff;font-weight:500}.option.disabled{opacity:.5;cursor:not-allowed;background:transparent!important}.option.hidden{display:none}.option.create-option{color:#2684ff;font-weight:500;background:#f0f6ff}.option.create-option:hover{background:#e6f2ff}.option input[type=checkbox]{cursor:pointer;width:16px;height:16px;margin:0;accent-color:#2684FF}.option-label{font-weight:500;color:#1f2937;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;line-height:1.4}.option-description{font-size:.875em;color:#6b7280;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;line-height:1.3}.check-icon{color:#2684ff;display:flex;align-items:center;margin-left:auto}.check-icon svg{fill:currentColor}.pin-button{background:none;border:none;color:#999;cursor:pointer;padding:4px;display:flex;align-items:center;justify-content:center;border-radius:3px;transition:all .15s;margin-left:auto;opacity:.5}.pin-button svg{fill:currentColor}.pin-button:hover{opacity:1;background:#0000000d}.pin-button.pinned{color:var(--ps-primary, #0052cc);opacity:1}.option.pinned-option{background:#0052cc0d;border-left:3px solid var(--ps-primary, #0052cc);padding-left:9px}.no-options,.loading-message{padding:16px 12px;text-align:center;color:#999;font-size:.95em}.info-message{padding:12px;margin:8px;border-radius:6px;font-size:.9em;display:flex;align-items:center;gap:8px}.info-message.max-selection{background:#fff4e5;color:#f57c00;border:1px solid #FFE0B2}.info-message.min-search{background:#e3f2fd;color:#1976d2;border:1px solid #BBDEFB}.info-message svg{flex-shrink:0}.rtl .select-actions,.rtl .tags,.rtl .option{flex-direction:row-reverse}.theme-blue .select-trigger:focus,.theme-blue .select-trigger.focused,.theme-blue .select-trigger.open{border-color:#2684ff;box-shadow:0 0 0 1px #2684ff}.theme-blue .option:hover:not(.disabled):not(.create-option),.theme-blue .option.highlighted{background:#deebff}.theme-blue .option.selected:not(.create-option){background:#e6f2ff}.theme-blue .tag{background:#e6f2ff;color:#0052cc;border-color:#cce0ff}.theme-blue .check-icon,.theme-blue .option.create-option{color:#2684ff}.theme-blue .spinner{border-top-color:#2684ff}.theme-blue .search-input:focus{border-color:#2684ff;box-shadow:0 0 0 1px #2684ff}.theme-purple .select-trigger:focus,.theme-purple .select-trigger.focused,.theme-purple .select-trigger.open{border-color:#9333ea;box-shadow:0 0 0 1px #9333ea}.theme-purple .option:hover:not(.disabled):not(.create-option),.theme-purple .option.highlighted{background:#f3e8ff}.theme-purple .option.selected:not(.create-option){background:#faf5ff}.theme-purple .tag{background:#faf5ff;color:#7e22ce;border-color:#e9d5ff}.theme-purple .check-icon,.theme-purple .option.create-option{color:#9333ea}.theme-purple .spinner{border-top-color:#9333ea}.theme-purple .search-input:focus{border-color:#9333ea;box-shadow:0 0 0 1px #9333ea}.theme-green .select-trigger:focus,.theme-green .select-trigger.focused,.theme-green .select-trigger.open{border-color:#10b981;box-shadow:0 0 0 1px #10b981}.theme-green .option:hover:not(.disabled):not(.create-option),.theme-green .option.highlighted{background:#d1fae5}.theme-green .option.selected:not(.create-option){background:#ecfdf5}.theme-green .tag{background:#ecfdf5;color:#059669;border-color:#a7f3d0}.theme-green .check-icon,.theme-green .option.create-option{color:#10b981}.theme-green .spinner{border-top-color:#10b981}.theme-green .search-input:focus{border-color:#10b981;box-shadow:0 0 0 1px #10b981}.theme-red .select-trigger:focus,.theme-red .select-trigger.focused,.theme-red .select-trigger.open{border-color:#ef4444;box-shadow:0 0 0 1px #ef4444}.theme-red .option:hover:not(.disabled):not(.create-option),.theme-red .option.highlighted{background:#fee2e2}.theme-red .option.selected:not(.create-option){background:#fef2f2}.theme-red .tag{background:#fef2f2;color:#dc2626;border-color:#fecaca}.theme-red .check-icon,.theme-red .option.create-option{color:#ef4444}.theme-red .spinner{border-top-color:#ef4444}.theme-red .search-input:focus{border-color:#ef4444;box-shadow:0 0 0 1px #ef4444}.theme-orange .select-trigger:focus,.theme-orange .select-trigger.focused,.theme-orange .select-trigger.open{border-color:#f97316;box-shadow:0 0 0 1px #f97316}.theme-orange .option:hover:not(.disabled):not(.create-option),.theme-orange .option.highlighted{background:#ffedd5}.theme-orange .option.selected:not(.create-option){background:#fff7ed}.theme-orange .tag{background:#fff7ed;color:#ea580c;border-color:#fed7aa}.theme-orange .check-icon,.theme-orange .option.create-option{color:#f97316}.theme-orange .spinner{border-top-color:#f97316}.theme-orange .search-input:focus{border-color:#f97316;box-shadow:0 0 0 1px #f97316}.theme-pink .select-trigger:focus,.theme-pink .select-trigger.focused,.theme-pink .select-trigger.open{border-color:#ec4899;box-shadow:0 0 0 1px #ec4899}.theme-pink .option:hover:not(.disabled):not(.create-option),.theme-pink .option.highlighted{background:#fce7f3}.theme-pink .option.selected:not(.create-option){background:#fdf2f8}.theme-pink .tag{background:#fdf2f8;color:#db2777;border-color:#fbcfe8}.theme-pink .check-icon,.theme-pink .option.create-option{color:#ec4899}.theme-pink .spinner{border-top-color:#ec4899}.theme-pink .search-input:focus{border-color:#ec4899;box-shadow:0 0 0 1px #ec4899}.theme-dark .select-trigger:focus,.theme-dark .select-trigger.focused,.theme-dark .select-trigger.open{border-color:#1f2937;box-shadow:0 0 0 1px #1f2937}.theme-dark .option:hover:not(.disabled):not(.create-option),.theme-dark .option.highlighted{background:#e5e7eb}.theme-dark .option.selected:not(.create-option){background:#f3f4f6}.theme-dark .tag{background:#f3f4f6;color:#111827;border-color:#d1d5db}.theme-dark .check-icon,.theme-dark .option.create-option{color:#1f2937}.theme-dark .spinner{border-top-color:#1f2937}.theme-dark .search-input:focus{border-color:#1f2937;box-shadow:0 0 0 1px #1f2937}.select-container.has-validation .select-trigger{transition:all .2s cubic-bezier(.4,0,.2,1)}.select-container.validation-error .select-trigger{border-color:#ef4444!important}.select-container.validation-error .select-trigger:focus,.select-container.validation-error .select-trigger.focused,.select-container.validation-error .select-trigger.open{border-color:#ef4444!important;box-shadow:0 0 0 3px #ef44441a,0 1px 2px #0000000d}.select-container.validation-warning .select-trigger{border-color:#f59e0b!important}.select-container.validation-warning .select-trigger:focus,.select-container.validation-warning .select-trigger.focused,.select-container.validation-warning .select-trigger.open{border-color:#f59e0b!important;box-shadow:0 0 0 3px #f59e0b1a,0 1px 2px #0000000d}.select-container.validation-success .select-trigger{border-color:#10b981!important}.select-container.validation-success .select-trigger:focus,.select-container.validation-success .select-trigger.focused,.select-container.validation-success .select-trigger.open{border-color:#10b981!important;box-shadow:0 0 0 3px #10b9811a,0 1px 2px #0000000d}.select-container.validation-info .select-trigger{border-color:#3b82f6!important}.select-container.validation-info .select-trigger:focus,.select-container.validation-info .select-trigger.focused,.select-container.validation-info .select-trigger.open{border-color:#3b82f6!important;box-shadow:0 0 0 3px #3b82f61a,0 1px 2px #0000000d}.validation-message{display:flex;align-items:center;gap:6px;margin-top:6px;font-size:13px;line-height:1.4;padding:4px 8px;border-radius:6px;transition:all .2s ease}.validation-message .validation-icon{display:flex;align-items:center;flex-shrink:0}.validation-message .validation-icon svg{display:block}.validation-message .validation-text{flex:1}.validation-message.validation-error{color:#dc2626;background:#fef2f2;border-left:3px solid #EF4444}.validation-message.validation-warning{color:#d97706;background:#fffbeb;border-left:3px solid #F59E0B}.validation-message.validation-success{color:#059669;background:#f0fdf4;border-left:3px solid #10B981}.validation-message.validation-info{color:#2563eb;background:#eff6ff;border-left:3px solid #3B82F6}.option.recent-option{position:relative;background:#fafbfc}.option.recent-option:after{content:\"\";position:absolute;left:0;top:0;bottom:0;width:3px;background:linear-gradient(to bottom,#2684ff,#0052cc);border-radius:0 3px 3px 0}.option.recent-option:hover:not(.disabled){background:#f4f5f7!important}.option.recent-option.selected{background:#e6f2ff!important}.option.recent-option.selected:hover:not(.disabled){background:#d4e8ff!important}.section-header{padding:8px 12px;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.5px;color:#6b7280;background:#f9fafb;border-bottom:1px solid #E5E7EB;position:sticky;top:0;z-index:2}.virtual-scroll-viewport{width:100%}.virtual-scroll-viewport ::ng-deep .cdk-virtual-scroll-content-wrapper{width:100%}.virtual-scroll-viewport ::ng-deep .cdk-virtual-scroll-spacer{display:none}.virtual-scroll-viewport .option{width:100%;box-sizing:border-box}@keyframes copyFlash{0%,to{background:transparent}50%{background:#2684ff1a}}.select-container.copying{animation:copyFlash .5s ease}.options-list.scrolling{position:relative}.options-list.scrolling:after{content:\"\";position:absolute;bottom:0;left:50%;transform:translate(-50%);width:20px;height:20px;border:2px solid #E5E7EB;border-top-color:#2684ff;border-radius:50%;animation:spin .8s linear infinite}.option[title]{cursor:help}.option.highlighted{position:relative}.option.highlighted:before{content:\"\";position:absolute;left:0;top:0;bottom:0;width:2px;background:#2684ff}@keyframes typeAheadPulse{0%,to{opacity:1}50%{opacity:.7}}.options-list.type-ahead-active .option.highlighted{animation:typeAheadPulse 1s ease-in-out}.select-container:focus-visible{outline:2px solid #2684FF;outline-offset:2px}@media (prefers-contrast: high){.select-trigger{border-width:2px}.option.highlighted{outline:2px solid currentColor;outline-offset:-2px}.validation-message{border-width:2px}}@media (prefers-reduced-motion: reduce){.select-trigger,.option,.tag,.dropdown,.validation-message{transition:none!important;animation:none!important}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i3.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i3.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: ClickOutsideDirective, selector: "[clickOutside]", outputs: ["clickOutside"] }, { kind: "ngmodule", type: ScrollingModule }, { kind: "directive", type: i4.CdkFixedSizeVirtualScroll, selector: "cdk-virtual-scroll-viewport[itemSize]", inputs: ["itemSize", "minBufferPx", "maxBufferPx"] }, { kind: "component", type: i4.CdkVirtualScrollViewport, selector: "cdk-virtual-scroll-viewport", inputs: ["orientation", "appendOnly"], outputs: ["scrolledIndexChange"] }, { kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i5.CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: i5.CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: i5.CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }, { kind: "directive", type: i5.CdkDragPlaceholder, selector: "ng-template[cdkDragPlaceholder]", inputs: ["data"] }, { kind: "pipe", type: i2.KeyValuePipe, name: "keyvalue" }], animations: selectAnimations });
1494
+ }], queries: [{ propertyName: "optionTemplate", first: true, predicate: ["optionTemplate"], descendants: true, read: TemplateRef }, { propertyName: "selectedOptionTemplate", first: true, predicate: ["selectedOptionTemplate"], descendants: true, read: TemplateRef }, { propertyName: "tagTemplate", first: true, predicate: ["tagTemplate"], descendants: true, read: TemplateRef }], viewQueries: [{ propertyName: "selectContainerRef", first: true, predicate: ["selectContainer"], descendants: true }, { propertyName: "searchInputRef", first: true, predicate: ["searchInput"], descendants: true }, { propertyName: "menuElementRef", first: true, predicate: ["menuRef"], descendants: true }, { propertyName: "virtualScrollViewport", first: true, predicate: CdkVirtualScrollViewport, descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div\n #selectContainer\n class=\"select-container {{selectSize}} {{containerSize}} theme-{{resolvedTheme()}} {{getValidationClass()}}\"\n [class.disabled]=\"isDisabled\"\n [class.rtl]=\"isRtl\"\n [class.has-validation]=\"validationState !== 'default'\"\n [class.compact]=\"compactMode\"\n [class.dark-mode]=\"isDarkMode()\"\n (clickOutside)=\"onClickOutside()\"\n role=\"combobox\"\n tabindex=\"0\"\n [attr.aria-controls]=\"'options-list'\"\n [attr.aria-expanded]=\"isOpen()\"\n [attr.aria-invalid]=\"validationState === 'error'\"\n (focus)=\"openMenuOnFocus && !isOpen() && toggleDropdown(); focus.emit()\"\n [style]=\"customStyles.container || ''\"\n>\n <!-- v2.3.0: Bulk Actions Bar (Above Position) -->\n @if (enableBulkActions && hasBulkActions() && bulkActionsPosition === 'above') {\n <div class=\"bulk-actions position-above\">\n <span class=\"bulk-label\">{{bulkActionsLabel}}</span>\n <div class=\"bulk-buttons\">\n @for (action of bulkActions; track action.id) {\n <button\n class=\"bulk-action-btn\"\n [disabled]=\"action.disabled || selectedOptions().length === 0\"\n (click)=\"executeBulkAction(action)\"\n [attr.aria-label]=\"action.label\"\n type=\"button\"\n >\n @if (action.icon) {\n <img [src]=\"action.icon\" alt=\"\" class=\"action-icon\" />\n }\n <span>{{action.label}}</span>\n </button>\n }\n </div>\n </div>\n }\n <!-- Main Select Trigger -->\n <div\n class=\"select-trigger\"\n [class.open]=\"isOpen()\"\n [class.focused]=\"isOpen()\"\n (click)=\"openMenuOnClick && toggleDropdown()\"\n [attr.tabindex]=\"isDisabled ? -1 : 0\"\n role=\"button\"\n aria-haspopup=\"listbox\"\n [attr.aria-expanded]=\"isOpen()\"\n [attr.aria-label]=\"placeholder\"\n >\n <!-- Selected Value Display -->\n <div class=\"select-value\">\n <!-- Multi-select Tags -->\n @if (isMulti && selectedOptions().length > 0) {\n <!-- v2.1.0: Drag-drop enabled tags -->\n @if (enableDragDrop) {\n <div class=\"tags\" cdkDropList cdkDropListOrientation=\"horizontal\" (cdkDropListDropped)=\"onTagsReorder($event)\" [class.dragging]=\"isDragging()\">\n @for (option of visibleTags(); track trackByValue($index, option)) {\n <span class=\"tag\" [class.disabled]=\"isDisabled\" cdkDrag (cdkDragStarted)=\"onDragStart()\" (cdkDragEnded)=\"onDragEnd()\" [@tag]>\n <div class=\"drag-handle\" cdkDragHandle>\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <path d=\"M9 3h2v2H9V3zm0 4h2v2H9V7zm0 4h2v2H9v-2zm0 4h2v2H9v-2zm0 4h2v2H9v-2zm4-16h2v2h-2V3zm0 4h2v2h-2V7zm0 4h2v2h-2v-2zm0 4h2v2h-2v-2zm0 4h2v2h-2v-2z\"/>\n </svg>\n </div>\n <span class=\"tag-label\">{{getOptionLabel(option)}}</span>\n @if (!isDisabled) {\n <button\n class=\"tag-remove\"\n (click)=\"removeOption(option, $event)\"\n [attr.aria-label]=\"'Remove ' + getOptionLabel(option)\"\n type=\"button\"\n >\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 20 20\">\n <path d=\"M14.348 14.849c-0.469 0.469-1.229 0.469-1.697 0l-2.651-3.030-2.651 3.029c-0.469 0.469-1.229 0.469-1.697 0-0.469-0.469-0.469-1.229 0-1.697l2.758-3.15-2.759-3.152c-0.469-0.469-0.469-1.228 0-1.697s1.228-0.469 1.697 0l2.652 3.031 2.651-3.031c0.469-0.469 1.228-0.469 1.697 0s0.469 1.229 0 1.697l-2.758 3.152 2.758 3.15c0.469 0.469 0.469 1.229 0 1.698z\"></path>\n </svg>\n </button>\n }\n <div *cdkDragPlaceholder class=\"drag-placeholder\">{{dragDropPlaceholder}}</div>\n </span>\n }\n </div>\n } @else {\n <div class=\"tags\">\n @for (option of visibleTags(); track trackByValue($index, option)) {\n <span class=\"tag\" [class.disabled]=\"isDisabled\" [@tag]>\n <span class=\"tag-label\">{{getOptionLabel(option)}}</span>\n @if (!isDisabled) {\n <button\n class=\"tag-remove\"\n (click)=\"removeOption(option, $event)\"\n [attr.aria-label]=\"'Remove ' + getOptionLabel(option)\"\n type=\"button\"\n >\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 20 20\">\n <path d=\"M14.348 14.849c-0.469 0.469-1.229 0.469-1.697 0l-2.651-3.030-2.651 3.029c-0.469 0.469-1.229 0.469-1.697 0-0.469-0.469-0.469-1.229 0-1.697l2.758-3.15-2.759-3.152c-0.469-0.469-0.469-1.228 0-1.697s1.228-0.469 1.697 0l2.652 3.031 2.651-3.031c0.469-0.469 1.228-0.469 1.697 0s0.469 1.229 0 1.697l-2.758 3.152 2.758 3.15c0.469 0.469 0.469 1.229 0 1.698z\"></path>\n </svg>\n </button>\n }\n </span>\n }\n </div>\n }\n\n <!-- v2.2.0: Tag overflow indicator -->\n @if (hiddenTagsCount() > 0) {\n @if (collapsibleTags) {\n <button\n class=\"tags-toggle\"\n (click)=\"toggleTagsExpanded()\"\n [attr.aria-label]=\"tagsExpanded() ? showLessTagsText : showAllTagsText\"\n type=\"button\"\n >\n {{ tagsExpanded() ? showLessTagsText : showAllTagsText }}\n </button>\n } @else {\n <span class=\"tags-overflow-indicator\">\n {{ getMoreTagsText() }}\n </span>\n }\n }\n } @else {\n <!-- Single Select or Placeholder -->\n <span class=\"placeholder\" [class.has-value]=\"selectedOptions().length > 0\">\n {{displayText()}}\n </span>\n }\n </div>\n\n <!-- Actions (Clear, Loading, Arrow) -->\n <div class=\"select-actions\">\n @if (isLoading || isLoadingAsync()) {\n <div class=\"spinner\"></div>\n }\n @if (isClearable && selectedOptions().length > 0 && !isDisabled && !isLoading && !isLoadingAsync()) {\n <button\n class=\"clear-button\"\n (click)=\"clearSelection($event)\"\n aria-label=\"Clear selection\"\n type=\"button\"\n >\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 20 20\">\n <path d=\"M14.348 14.849c-0.469 0.469-1.229 0.469-1.697 0l-2.651-3.030-2.651 3.029c-0.469 0.469-1.229 0.469-1.697 0-0.469-0.469-0.469-1.229 0-1.697l2.758-3.15-2.759-3.152c-0.469-0.469-0.469-1.228 0-1.697s1.228-0.469 1.697 0l2.652 3.031 2.651-3.031c0.469-0.469 1.228-0.469 1.697 0s0.469 1.229 0 1.697l-2.758 3.152 2.758 3.15c0.469 0.469 0.469 1.229 0 1.698z\"></path>\n </svg>\n </button>\n }\n <span class=\"separator\"></span>\n <span class=\"arrow\" [class.open]=\"isOpen()\">\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 20 20\">\n <path d=\"M4.516 7.548c0.436-0.446 1.043-0.481 1.576 0l3.908 3.747 3.908-3.747c0.533-0.481 1.141-0.446 1.574 0 0.436 0.445 0.408 1.197 0 1.615-0.406 0.418-4.695 4.502-4.695 4.502-0.217 0.223-0.502 0.335-0.787 0.335s-0.57-0.112-0.789-0.335c0 0-4.287-4.084-4.695-4.502s-0.436-1.17 0-1.615z\"></path>\n </svg>\n </span>\n </div>\n </div>\n\n <!-- Dropdown Menu -->\n @if (isOpen()) {\n <div\n #menuRef\n class=\"dropdown {{menuPlacement}}\"\n [class.fixed]=\"menuPosition === 'fixed'\"\n [style.max-height]=\"maxHeight\"\n role=\"listbox\"\n [attr.aria-multiselectable]=\"isMulti\"\n [@dropdown]\n >\n <!-- Search Input -->\n @if (isSearchable) {\n <div class=\"search-container\">\n <input\n #searchInput\n type=\"text\"\n class=\"search-input\"\n placeholder=\"Search...\"\n [value]=\"searchTerm()\"\n (input)=\"onSearchChange($any($event.target).value)\"\n (click)=\"$event.stopPropagation()\"\n aria-label=\"Search options\"\n aria-autocomplete=\"list\"\n />\n </div>\n }\n\n <!-- Options List -->\n <div\n class=\"options-list\"\n id=\"options-list\"\n (scroll)=\"onOptionsScroll($event)\"\n >\n <!-- Max Selection Message -->\n @if (isMaxSelectionReached()) {\n <div class=\"info-message max-selection\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 20 20\" fill=\"currentColor\">\n <path d=\"M10 0C4.477 0 0 4.477 0 10s4.477 10 10 10 10-4.477 10-10S15.523 0 10 0zm1 15H9v-2h2v2zm0-4H9V5h2v6z\"/>\n </svg>\n {{maxSelectedMessage}}\n </div>\n }\n\n <!-- Min Search Length Message -->\n @if (showMinSearchMessage()) {\n <div class=\"info-message min-search\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 20 20\" fill=\"currentColor\">\n <path d=\"M10 0C4.477 0 0 4.477 0 10s4.477 10 10 10 10-4.477 10-10S15.523 0 10 0zm1 15H9v-2h2v2zm0-4H9V5h2v6z\"/>\n </svg>\n {{minSearchMessage}} ({{searchTerm().length}}/{{minSearchLength}})\n </div>\n }\n\n @if (isLoadingAsync()) {\n <div class=\"loading-message\">{{loadingMessage()}}</div>\n } @else if (displayOptions().length === 0 && !showMinSearchMessage()) {\n <div class=\"no-options\">\n {{searchTerm() ? emptySearchText : emptyStateText}}\n </div>\n } @else if (!showMinSearchMessage()) {\n <!-- Select All (Multi-select only) -->\n @if (isMulti && showSelectAll && !searchTerm()) {\n <div class=\"select-all-container\">\n <button\n class=\"select-all-button\"\n (click)=\"allOptionsSelected() ? deselectAll() : selectAll()\"\n type=\"button\"\n >\n <input\n type=\"checkbox\"\n [checked]=\"allOptionsSelected()\"\n [indeterminate]=\"someOptionsSelected()\"\n tabindex=\"-1\"\n aria-hidden=\"true\"\n readonly\n />\n <span class=\"select-all-text\">\n {{allOptionsSelected() ? deselectAllText : selectAllText}}\n </span>\n <span class=\"select-all-count\">\n ({{selectedOptions().length}}/{{getEnabledOptionsCount()}})\n </span>\n </button>\n </div>\n }\n\n <!-- Grouped Options -->\n @if (isGrouped && groupedOptions()) {\n @for (group of groupedOptions() | keyvalue; track trackByGroup($index, [$any(group.key), $any(group.value)])) {\n <div class=\"option-group\">\n <div class=\"option-group-label\">{{group.key}}</div>\n @for (option of $any(group.value); track trackByValue($index, option); let idx = $index) {\n <div\n class=\"option\"\n [class.selected]=\"isSelected(option)\"\n [class.highlighted]=\"idx === highlightedIndex()\"\n [class.disabled]=\"isOptionDisabled(option) || (isMaxSelectionReached() && !isSelected(option))\"\n [class.hidden]=\"hideSelectedOptions && isSelected(option)\"\n [class.create-option]=\"option.__isCreate__\"\n (click)=\"selectOption(option)\"\n (keydown)=\"$event.key === 'Enter' && selectOption(option)\"\n role=\"option\"\n [attr.aria-selected]=\"isSelected(option)\"\n [attr.aria-disabled]=\"isOptionDisabled(option) || (isMaxSelectionReached() && !isSelected(option))\"\n tabindex=\"-1\"\n >\n <!-- Checkbox for multi-select -->\n @if (isMulti) {\n <input\n type=\"checkbox\"\n [checked]=\"isSelected(option)\"\n tabindex=\"-1\"\n aria-hidden=\"true\"\n readonly\n />\n }\n\n <!-- Option Icon -->\n @if (showOptionIcons && option.icon) {\n <div class=\"option-icon\">\n @if (option.icon.includes('<')) {\n <div [innerHTML]=\"sanitizeHtml(option.icon)\"></div>\n } @else {\n <img [src]=\"option.icon\" [alt]=\"getOptionLabel(option)\" />\n }\n </div>\n }\n\n <!-- Option Content -->\n <div class=\"option-content\">\n <div class=\"option-label\" [innerHTML]=\"highlightSearchTerm(getOptionLabel(option))\"></div>\n @if (option.description) {\n <div class=\"option-description\">{{option.description}}</div>\n }\n </div>\n\n <!-- Option Badge -->\n @if (showOptionBadges && option.badge) {\n <span\n class=\"option-badge\"\n [style.background-color]=\"option.badgeColor || currentTheme().primary\"\n >\n {{option.badge}}\n </span>\n }\n\n <!-- v2.1.0: Pin button -->\n @if (enablePinning) {\n <button\n class=\"pin-button\"\n [class.pinned]=\"isPinned(option)\"\n (click)=\"togglePin(option, $event)\"\n [attr.aria-label]=\"isPinned(option) ? 'Unpin option' : 'Pin option'\"\n type=\"button\"\n >\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n @if (isPinned(option)) {\n <path d=\"M16,12V4H17V2H7V4H8V12L6,14V16H11.2V22H12.8V16H18V14L16,12Z\"/>\n } @else {\n <path d=\"M16,12V4H17V2H7V4H8V12L6,14V16H11.2V22H12.8V16H18V14L16,12M8.8,14L10,12.8V4H14V12.8L15.2,14H8.8Z\"/>\n }\n </svg>\n </button>\n }\n\n <!-- Check Icon for selected -->\n @if (!isMulti && isSelected(option)) {\n <svg class=\"check-icon\" width=\"16\" height=\"16\" viewBox=\"0 0 20 20\">\n <path d=\"M7.629,14.566c0.125,0.125,0.291,0.188,0.456,0.188c0.164,0,0.329-0.062,0.456-0.188l8.219-8.221c0.252-0.252,0.252-0.659,0-0.911c-0.252-0.252-0.659-0.252-0.911,0l-7.764,7.763L4.152,9.267c-0.252-0.251-0.66-0.251-0.911,0c-0.252,0.252-0.252,0.66,0,0.911L7.629,14.566z\"></path>\n </svg>\n }\n </div>\n }\n </div>\n }\n } @else {\n <!-- v1.2.0: Recent Selections Header -->\n @if (showRecentSelections && !searchTerm() && recentSelections().length > 0) {\n <div class=\"section-header\">{{recentSelectionsLabel}}</div>\n }\n\n <!-- Regular (Ungrouped) Options - Virtual Scroll or Standard -->\n @if (enableVirtualScroll) {\n <!-- Virtual Scroll Mode -->\n <cdk-virtual-scroll-viewport\n [itemSize]=\"virtualScrollItemSize\"\n [minBufferPx]=\"virtualScrollMinBufferPx\"\n [maxBufferPx]=\"virtualScrollMaxBufferPx\"\n [style.height.px]=\"maxHeight\"\n class=\"virtual-scroll-viewport\"\n >\n @for (option of (showRecentSelections ? displayOptionsWithRecent() : displayOptions()); track trackByValue($index, option); let idx = $index) {\n <div\n class=\"option\"\n [class.selected]=\"isSelected(option)\"\n [class.highlighted]=\"idx === highlightedIndex()\"\n [class.disabled]=\"isOptionDisabled(option) || (isMaxSelectionReached() && !isSelected(option))\"\n [class.hidden]=\"hideSelectedOptions && isSelected(option)\"\n [class.create-option]=\"option.__isCreate__\"\n [class.recent-option]=\"option.__isRecent__\"\n (click)=\"selectOption(option)\"\n (keydown)=\"$event.key === 'Enter' && selectOption(option)\"\n (mouseenter)=\"showTooltip(option, idx)\"\n (mouseleave)=\"hideTooltip()\"\n role=\"option\"\n [attr.aria-selected]=\"isSelected(option)\"\n [attr.aria-disabled]=\"isOptionDisabled(option) || (isMaxSelectionReached() && !isSelected(option))\"\n [attr.title]=\"showTooltips ? getTooltipContent(option) : null\"\n tabindex=\"-1\"\n >\n <!-- Custom Template or Default Rendering -->\n @if (optionTemplate) {\n <ng-container\n *ngTemplateOutlet=\"optionTemplate; context: { $implicit: option, index: idx, selected: isSelected(option) }\"\n ></ng-container>\n } @else {\n <!-- Default Option Content (same as before) -->\n @if (isMulti) {\n <input\n type=\"checkbox\"\n [checked]=\"isSelected(option)\"\n tabindex=\"-1\"\n aria-hidden=\"true\"\n readonly\n />\n }\n\n @if (showOptionIcons && option.icon) {\n <div class=\"option-icon\">\n @if (option.icon.includes('<')) {\n <div [innerHTML]=\"sanitizeHtml(option.icon)\"></div>\n } @else {\n <img [src]=\"option.icon\" [alt]=\"getOptionLabel(option)\" />\n }\n </div>\n }\n\n <div class=\"option-content\">\n <div class=\"option-label\" [innerHTML]=\"highlightSearchTerm(getOptionLabel(option))\"></div>\n @if (option.description) {\n <div class=\"option-description\">{{option.description}}</div>\n }\n </div>\n\n @if (showOptionBadges && option.badge) {\n <span\n class=\"option-badge\"\n [style.background-color]=\"option.badgeColor || currentTheme().primary\"\n >\n {{option.badge}}\n </span>\n }\n\n <!-- v2.1.0: Pin button -->\n @if (enablePinning) {\n <button\n class=\"pin-button\"\n [class.pinned]=\"isPinned(option)\"\n (click)=\"togglePin(option, $event)\"\n [attr.aria-label]=\"isPinned(option) ? 'Unpin option' : 'Pin option'\"\n type=\"button\"\n >\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n @if (isPinned(option)) {\n <path d=\"M16,12V4H17V2H7V4H8V12L6,14V16H11.2V22H12.8V16H18V14L16,12Z\"/>\n } @else {\n <path d=\"M16,12V4H17V2H7V4H8V12L6,14V16H11.2V22H12.8V16H18V14L16,12M8.8,14L10,12.8V4H14V12.8L15.2,14H8.8Z\"/>\n }\n </svg>\n </button>\n }\n\n @if (!isMulti && isSelected(option)) {\n <svg class=\"check-icon\" width=\"16\" height=\"16\" viewBox=\"0 0 20 20\">\n <path d=\"M7.629,14.566c0.125,0.125,0.291,0.188,0.456,0.188c0.164,0,0.329-0.062,0.456-0.188l8.219-8.221c0.252-0.252,0.252-0.659,0-0.911c-0.252-0.252-0.659-0.252-0.911,0l-7.764,7.763L4.152,9.267c-0.252-0.251-0.66-0.251-0.911,0c-0.252,0.252-0.252,0.66,0,0.911L7.629,14.566z\"></path>\n </svg>\n }\n }\n </div>\n }\n </cdk-virtual-scroll-viewport>\n } @else {\n <!-- Standard Rendering (Non-Virtual Scroll) -->\n @for (option of (showRecentSelections ? displayOptionsWithRecent() : displayOptions()); track trackByValue($index, option); let idx = $index) {\n <div\n class=\"option\"\n [class.selected]=\"isSelected(option)\"\n [class.highlighted]=\"idx === highlightedIndex()\"\n [class.disabled]=\"isOptionDisabled(option) || (isMaxSelectionReached() && !isSelected(option))\"\n [class.hidden]=\"hideSelectedOptions && isSelected(option)\"\n [class.create-option]=\"option.__isCreate__\"\n [class.recent-option]=\"option.__isRecent__\"\n (click)=\"selectOption(option)\"\n (keydown)=\"$event.key === 'Enter' && selectOption(option)\"\n (mouseenter)=\"showTooltip(option, idx)\"\n (mouseleave)=\"hideTooltip()\"\n role=\"option\"\n [attr.aria-selected]=\"isSelected(option)\"\n [attr.aria-disabled]=\"isOptionDisabled(option) || (isMaxSelectionReached() && !isSelected(option))\"\n [attr.title]=\"showTooltips ? getTooltipContent(option) : null\"\n tabindex=\"-1\"\n >\n <!-- Custom Template or Default Rendering -->\n @if (optionTemplate) {\n <ng-container\n *ngTemplateOutlet=\"optionTemplate; context: { $implicit: option, index: idx, selected: isSelected(option) }\"\n ></ng-container>\n } @else {\n <!-- Default Option Content -->\n @if (isMulti) {\n <input\n type=\"checkbox\"\n [checked]=\"isSelected(option)\"\n tabindex=\"-1\"\n aria-hidden=\"true\"\n readonly\n />\n }\n\n @if (showOptionIcons && option.icon) {\n <div class=\"option-icon\">\n @if (option.icon.includes('<')) {\n <div [innerHTML]=\"sanitizeHtml(option.icon)\"></div>\n } @else {\n <img [src]=\"option.icon\" [alt]=\"getOptionLabel(option)\" />\n }\n </div>\n }\n\n <div class=\"option-content\">\n <div class=\"option-label\" [innerHTML]=\"highlightSearchTerm(getOptionLabel(option))\"></div>\n @if (option.description) {\n <div class=\"option-description\">{{option.description}}</div>\n }\n </div>\n\n @if (showOptionBadges && option.badge) {\n <span\n class=\"option-badge\"\n [style.background-color]=\"option.badgeColor || currentTheme().primary\"\n >\n {{option.badge}}\n </span>\n }\n\n <!-- v2.1.0: Pin button -->\n @if (enablePinning) {\n <button\n class=\"pin-button\"\n [class.pinned]=\"isPinned(option)\"\n (click)=\"togglePin(option, $event)\"\n [attr.aria-label]=\"isPinned(option) ? 'Unpin option' : 'Pin option'\"\n type=\"button\"\n >\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n @if (isPinned(option)) {\n <path d=\"M16,12V4H17V2H7V4H8V12L6,14V16H11.2V22H12.8V16H18V14L16,12Z\"/>\n } @else {\n <path d=\"M16,12V4H17V2H7V4H8V12L6,14V16H11.2V22H12.8V16H18V14L16,12M8.8,14L10,12.8V4H14V12.8L15.2,14H8.8Z\"/>\n }\n </svg>\n </button>\n }\n\n @if (!isMulti && isSelected(option)) {\n <svg class=\"check-icon\" width=\"16\" height=\"16\" viewBox=\"0 0 20 20\">\n <path d=\"M7.629,14.566c0.125,0.125,0.291,0.188,0.456,0.188c0.164,0,0.329-0.062,0.456-0.188l8.219-8.221c0.252-0.252,0.252-0.659,0-0.911c-0.252-0.252-0.659-0.252-0.911,0l-7.764,7.763L4.152,9.267c-0.252-0.251-0.66-0.251-0.911,0c-0.252,0.252-0.252,0.66,0,0.911L7.629,14.566z\"></path>\n </svg>\n }\n }\n </div>\n }\n }\n }\n }\n </div>\n </div>\n }\n\n <!-- v1.2.0: Validation Message -->\n @if (validationMessage && validationState !== 'default') {\n <div class=\"validation-message {{getValidationClass()}}\">\n @if (showValidationIcon) {\n <span class=\"validation-icon\">\n @if (validationState === 'error') {\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 20 20\" fill=\"currentColor\">\n <path d=\"M10 0C4.477 0 0 4.477 0 10s4.477 10 10 10 10-4.477 10-10S15.523 0 10 0zm1 15H9v-2h2v2zm0-4H9V5h2v6z\"/>\n </svg>\n } @else if (validationState === 'warning') {\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 20 20\" fill=\"currentColor\">\n <path d=\"M10 0C4.477 0 0 4.477 0 10s4.477 10 10 10 10-4.477 10-10S15.523 0 10 0zm1 15H9v-2h2v2zm0-4H9V5h2v6z\"/>\n </svg>\n } @else if (validationState === 'success') {\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 20 20\" fill=\"currentColor\">\n <path d=\"M10 0C4.477 0 0 4.477 0 10s4.477 10 10 10 10-4.477 10-10S15.523 0 10 0zm-1.707 13.707l-3.5-3.5 1.414-1.414L9 11.586l5.293-5.293 1.414 1.414-6.5 6.5z\"/>\n </svg>\n } @else if (validationState === 'info') {\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 20 20\" fill=\"currentColor\">\n <path d=\"M10 0C4.477 0 0 4.477 0 10s4.477 10 10 10 10-4.477 10-10S15.523 0 10 0zm1 15H9v-6h2v6zm0-8H9V5h2v2z\"/>\n </svg>\n }\n </span>\n }\n <span class=\"validation-text\">{{validationMessage}}</span>\n </div>\n }\n</div>\n\n<!-- Hidden native select for form compatibility -->\n@if (isMulti) {\n <select [name]=\"name\" [id]=\"id\" multiple style=\"display: none;\" [attr.aria-hidden]=\"true\">\n @for (option of selectedOptions(); track trackByValue($index, option)) {\n <option [value]=\"getOptionValue(option)\" selected>{{getOptionLabel(option)}}</option>\n }\n </select>\n} @else {\n @if (selectedOptions().length > 0) {\n <select [name]=\"name\" [id]=\"id\" style=\"display: none;\" [attr.aria-hidden]=\"true\">\n <option [value]=\"getOptionValue(selectedOptions()[0])\" selected>{{getOptionLabel(selectedOptions()[0])}}</option>\n </select>\n }\n}\n", styles: [":host{display:block;width:100%}.select-container{position:relative;width:100%;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif}.smaller{font-size:11px}.small{font-size:13px}.medium{font-size:14px}.large{font-size:16px}.larger{font-size:18px}.xs .select-trigger{min-height:28px;padding:4px 8px}.sm .select-trigger{min-height:32px;padding:6px 10px}.md .select-trigger{min-height:40px;padding:8px 12px}.lg .select-trigger{min-height:48px;padding:10px 14px}.xl .select-trigger{min-height:56px;padding:12px 16px}.select-container.disabled{opacity:.6;cursor:not-allowed}.select-container.rtl{direction:rtl}.select-trigger{display:flex;align-items:center;justify-content:space-between;padding:8px 12px;background:#fff;border:1.5px solid #D1D5DB;border-radius:10px;cursor:pointer;transition:all .2s cubic-bezier(.4,0,.2,1);min-height:38px;gap:8px;box-shadow:0 1px 2px #0000000d}.select-trigger:hover:not(.disabled){border-color:#9ca3af;box-shadow:0 2px 4px #00000014}.select-trigger:focus,.select-trigger.focused{outline:none;border-color:#2684ff;box-shadow:0 0 0 3px #2684ff1a,0 1px 2px #0000000d}.select-trigger.open{border-color:#2684ff;box-shadow:0 0 0 3px #2684ff1a,0 1px 2px #0000000d}.select-value{flex:1;display:flex;align-items:center;min-width:0;gap:4px}.placeholder{color:#999;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.placeholder.has-value{color:#333}.tags{display:flex;flex-wrap:wrap;gap:4px;flex:1}.tag{display:inline-flex;align-items:center;gap:4px;padding:4px 8px;background:#e6f2ff;color:#0052cc;border-radius:6px;font-size:.875em;font-weight:500;white-space:nowrap;border:1px solid #CCE0FF;box-shadow:0 1px 2px #0000000d}.tag.disabled{background:#f0f0f0;color:#666;border-color:#d9d9d9}.tag-label{line-height:1.2}.tag-remove{background:none;border:none;color:inherit;cursor:pointer;padding:0;display:flex;align-items:center;justify-content:center;border-radius:2px;transition:all .15s}.tag-remove:hover{background:#0052cc26}.tag-remove svg{fill:currentColor}.tags.dragging .tag{cursor:move}.drag-handle{display:flex;align-items:center;justify-content:center;cursor:move;padding:0;margin-left:-4px;opacity:.5;transition:opacity .15s}.drag-handle svg{fill:currentColor}.drag-handle:hover{opacity:.8}.cdk-drag-preview{box-shadow:0 5px 15px #0003;opacity:.8}.cdk-drag-animating{transition:transform .25s cubic-bezier(0,0,.2,1)}.drag-placeholder{background:#0052cc1a;border:2px dashed var(--ps-primary, #0052cc);border-radius:6px;padding:4px 8px;min-height:28px;display:flex;align-items:center;justify-content:center;color:var(--ps-primary, #0052cc);font-size:.75em;opacity:.6}.cdk-drop-list-dragging .cdk-drag{transition:transform .25s cubic-bezier(0,0,.2,1)}.tags-toggle{background:none;border:none;color:var(--ps-primary, #0052cc);cursor:pointer;font-size:.875em;font-weight:500;padding:4px 8px;border-radius:4px;transition:all .15s;white-space:nowrap}.tags-toggle:hover{background:#0052cc1a;text-decoration:underline}.tags-toggle:focus{outline:2px solid var(--ps-primary, #0052cc);outline-offset:2px}.tags-overflow-indicator{display:inline-flex;align-items:center;padding:4px 8px;background:#f3f4f6;color:#6b7280;border-radius:6px;font-size:.875em;font-weight:500;white-space:nowrap;border:1px solid #d1d5db}.option-label mark{background-color:#ffeb3b;color:#000;padding:0 2px;border-radius:2px;font-weight:600}.select-actions{display:flex;align-items:center;gap:4px;flex-shrink:0}.clear-button{background:none;border:none;color:#999;cursor:pointer;padding:4px;display:flex;align-items:center;justify-content:center;border-radius:3px;transition:all .15s}.clear-button:hover{color:#333;background:#f0f0f0}.clear-button svg{fill:currentColor}.separator{width:1px;height:24px;background:#ccc;align-self:stretch}.arrow{color:#999;transition:transform .2s cubic-bezier(.4,0,.2,1);display:flex;align-items:center;padding:4px}.arrow svg{fill:currentColor}.arrow.open{transform:rotate(180deg)}.spinner{width:16px;height:16px;border:2px solid #f3f3f3;border-top:2px solid #2684FF;border-radius:50%;animation:spin .6s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}.dropdown{position:absolute;top:calc(100% + 8px);left:0;right:0;background:#fffffffa;border:1px solid #E5E7EB;border-radius:12px;box-shadow:0 20px 25px -5px #0000001a,0 10px 10px -5px #0000000a,0 0 0 1px #0000000d;z-index:1000;overflow:hidden;backdrop-filter:blur(12px) saturate(180%);-webkit-backdrop-filter:blur(12px) saturate(180%)}.dropdown.fixed{position:fixed}.dropdown.top{top:auto;bottom:calc(100% + 4px)}.search-container{padding:8px;border-bottom:1px solid #f0f0f0;background:#fff}.search-input{width:100%;padding:6px 8px;border:1px solid #cccccc;border-radius:3px;font-size:inherit;outline:none;transition:border-color .15s}.search-input:focus{border-color:#2684ff;box-shadow:0 0 0 1px #2684ff}.options-list{max-height:inherit;overflow-y:auto;overflow-x:hidden;padding:4px 0}.options-list::-webkit-scrollbar{width:8px}.options-list::-webkit-scrollbar-track{background:transparent}.options-list::-webkit-scrollbar-thumb{background:#d9d9d9;border-radius:4px}.options-list::-webkit-scrollbar-thumb:hover{background:#b3b3b3}.select-all-container{padding:8px;border-bottom:1px solid #E5E7EB;background:#f9fafb}.select-all-button{width:100%;display:flex;align-items:center;gap:8px;padding:6px 8px;background:#fff;border:1px solid #D1D5DB;border-radius:6px;cursor:pointer;transition:all .15s;font-size:inherit;color:inherit;font-family:inherit}.select-all-button:hover{background:#f3f4f6;border-color:#9ca3af}.select-all-button input[type=checkbox]{cursor:pointer;width:16px;height:16px;margin:0;accent-color:#2684FF}.select-all-text{flex:1;text-align:left;font-weight:500}.select-all-count{color:#6b7280;font-size:.9em}.option-group{margin:4px 0}.option-group-label{padding:8px 12px 4px;font-size:.75em;font-weight:600;color:#6b7280;text-transform:uppercase;letter-spacing:.05em;background:#f9fafb;border-bottom:1px solid #E5E7EB;position:sticky;top:0;z-index:1}.option-group .option{padding-left:20px}.option{display:flex;align-items:center;gap:10px;padding:10px 12px;cursor:pointer;transition:all .15s cubic-bezier(.4,0,.2,1);position:relative;min-height:40px}.option-content{flex:1;display:flex;flex-direction:column;gap:2px;min-width:0}.option-icon{display:flex;align-items:center;justify-content:center;width:32px;height:32px;flex-shrink:0;border-radius:6px;overflow:hidden;background:#f3f4f6}.option-icon img{width:100%;height:100%;object-fit:cover}.option-badge{padding:2px 8px;border-radius:12px;font-size:.75em;font-weight:500;color:#374151;background:#e5e7eb;flex-shrink:0;white-space:nowrap}.option:hover:not(.disabled):not(.create-option){background:#deebff}.option.highlighted{background:#deebff}.option.selected:not(.create-option){background:#e6f2ff;font-weight:500}.option.disabled{opacity:.5;cursor:not-allowed;background:transparent!important}.option.hidden{display:none}.option.create-option{color:#2684ff;font-weight:500;background:#f0f6ff}.option.create-option:hover{background:#e6f2ff}.option input[type=checkbox]{cursor:pointer;width:16px;height:16px;margin:0;accent-color:#2684FF}.option-label{font-weight:500;color:#1f2937;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;line-height:1.4}.option-description{font-size:.875em;color:#6b7280;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;line-height:1.3}.check-icon{color:#2684ff;display:flex;align-items:center;margin-left:auto}.check-icon svg{fill:currentColor}.pin-button{background:none;border:none;color:#999;cursor:pointer;padding:4px;display:flex;align-items:center;justify-content:center;border-radius:3px;transition:all .15s;margin-left:auto;opacity:.5}.pin-button svg{fill:currentColor}.pin-button:hover{opacity:1;background:#0000000d}.pin-button.pinned{color:var(--ps-primary, #0052cc);opacity:1}.option.pinned-option{background:#0052cc0d;border-left:3px solid var(--ps-primary, #0052cc);padding-left:9px}.no-options,.loading-message{padding:16px 12px;text-align:center;color:#999;font-size:.95em}.info-message{padding:12px;margin:8px;border-radius:6px;font-size:.9em;display:flex;align-items:center;gap:8px}.info-message.max-selection{background:#fff4e5;color:#f57c00;border:1px solid #FFE0B2}.info-message.min-search{background:#e3f2fd;color:#1976d2;border:1px solid #BBDEFB}.info-message svg{flex-shrink:0}.rtl .select-actions,.rtl .tags,.rtl .option{flex-direction:row-reverse}.theme-blue .select-trigger:focus,.theme-blue .select-trigger.focused,.theme-blue .select-trigger.open{border-color:#2684ff;box-shadow:0 0 0 1px #2684ff}.theme-blue .option:hover:not(.disabled):not(.create-option),.theme-blue .option.highlighted{background:#deebff}.theme-blue .option.selected:not(.create-option){background:#e6f2ff}.theme-blue .tag{background:#e6f2ff;color:#0052cc;border-color:#cce0ff}.theme-blue .check-icon,.theme-blue .option.create-option{color:#2684ff}.theme-blue .spinner{border-top-color:#2684ff}.theme-blue .search-input:focus{border-color:#2684ff;box-shadow:0 0 0 1px #2684ff}.theme-purple .select-trigger:focus,.theme-purple .select-trigger.focused,.theme-purple .select-trigger.open{border-color:#9333ea;box-shadow:0 0 0 1px #9333ea}.theme-purple .option:hover:not(.disabled):not(.create-option),.theme-purple .option.highlighted{background:#f3e8ff}.theme-purple .option.selected:not(.create-option){background:#faf5ff}.theme-purple .tag{background:#faf5ff;color:#7e22ce;border-color:#e9d5ff}.theme-purple .check-icon,.theme-purple .option.create-option{color:#9333ea}.theme-purple .spinner{border-top-color:#9333ea}.theme-purple .search-input:focus{border-color:#9333ea;box-shadow:0 0 0 1px #9333ea}.theme-green .select-trigger:focus,.theme-green .select-trigger.focused,.theme-green .select-trigger.open{border-color:#10b981;box-shadow:0 0 0 1px #10b981}.theme-green .option:hover:not(.disabled):not(.create-option),.theme-green .option.highlighted{background:#d1fae5}.theme-green .option.selected:not(.create-option){background:#ecfdf5}.theme-green .tag{background:#ecfdf5;color:#059669;border-color:#a7f3d0}.theme-green .check-icon,.theme-green .option.create-option{color:#10b981}.theme-green .spinner{border-top-color:#10b981}.theme-green .search-input:focus{border-color:#10b981;box-shadow:0 0 0 1px #10b981}.theme-red .select-trigger:focus,.theme-red .select-trigger.focused,.theme-red .select-trigger.open{border-color:#ef4444;box-shadow:0 0 0 1px #ef4444}.theme-red .option:hover:not(.disabled):not(.create-option),.theme-red .option.highlighted{background:#fee2e2}.theme-red .option.selected:not(.create-option){background:#fef2f2}.theme-red .tag{background:#fef2f2;color:#dc2626;border-color:#fecaca}.theme-red .check-icon,.theme-red .option.create-option{color:#ef4444}.theme-red .spinner{border-top-color:#ef4444}.theme-red .search-input:focus{border-color:#ef4444;box-shadow:0 0 0 1px #ef4444}.theme-orange .select-trigger:focus,.theme-orange .select-trigger.focused,.theme-orange .select-trigger.open{border-color:#f97316;box-shadow:0 0 0 1px #f97316}.theme-orange .option:hover:not(.disabled):not(.create-option),.theme-orange .option.highlighted{background:#ffedd5}.theme-orange .option.selected:not(.create-option){background:#fff7ed}.theme-orange .tag{background:#fff7ed;color:#ea580c;border-color:#fed7aa}.theme-orange .check-icon,.theme-orange .option.create-option{color:#f97316}.theme-orange .spinner{border-top-color:#f97316}.theme-orange .search-input:focus{border-color:#f97316;box-shadow:0 0 0 1px #f97316}.theme-pink .select-trigger:focus,.theme-pink .select-trigger.focused,.theme-pink .select-trigger.open{border-color:#ec4899;box-shadow:0 0 0 1px #ec4899}.theme-pink .option:hover:not(.disabled):not(.create-option),.theme-pink .option.highlighted{background:#fce7f3}.theme-pink .option.selected:not(.create-option){background:#fdf2f8}.theme-pink .tag{background:#fdf2f8;color:#db2777;border-color:#fbcfe8}.theme-pink .check-icon,.theme-pink .option.create-option{color:#ec4899}.theme-pink .spinner{border-top-color:#ec4899}.theme-pink .search-input:focus{border-color:#ec4899;box-shadow:0 0 0 1px #ec4899}.theme-dark .select-trigger:focus,.theme-dark .select-trigger.focused,.theme-dark .select-trigger.open{border-color:#1f2937;box-shadow:0 0 0 1px #1f2937}.theme-dark .option:hover:not(.disabled):not(.create-option),.theme-dark .option.highlighted{background:#e5e7eb}.theme-dark .option.selected:not(.create-option){background:#f3f4f6}.theme-dark .tag{background:#f3f4f6;color:#111827;border-color:#d1d5db}.theme-dark .check-icon,.theme-dark .option.create-option{color:#1f2937}.theme-dark .spinner{border-top-color:#1f2937}.theme-dark .search-input:focus{border-color:#1f2937;box-shadow:0 0 0 1px #1f2937}.select-container.has-validation .select-trigger{transition:all .2s cubic-bezier(.4,0,.2,1)}.select-container.validation-error .select-trigger{border-color:#ef4444!important}.select-container.validation-error .select-trigger:focus,.select-container.validation-error .select-trigger.focused,.select-container.validation-error .select-trigger.open{border-color:#ef4444!important;box-shadow:0 0 0 3px #ef44441a,0 1px 2px #0000000d}.select-container.validation-warning .select-trigger{border-color:#f59e0b!important}.select-container.validation-warning .select-trigger:focus,.select-container.validation-warning .select-trigger.focused,.select-container.validation-warning .select-trigger.open{border-color:#f59e0b!important;box-shadow:0 0 0 3px #f59e0b1a,0 1px 2px #0000000d}.select-container.validation-success .select-trigger{border-color:#10b981!important}.select-container.validation-success .select-trigger:focus,.select-container.validation-success .select-trigger.focused,.select-container.validation-success .select-trigger.open{border-color:#10b981!important;box-shadow:0 0 0 3px #10b9811a,0 1px 2px #0000000d}.select-container.validation-info .select-trigger{border-color:#3b82f6!important}.select-container.validation-info .select-trigger:focus,.select-container.validation-info .select-trigger.focused,.select-container.validation-info .select-trigger.open{border-color:#3b82f6!important;box-shadow:0 0 0 3px #3b82f61a,0 1px 2px #0000000d}.validation-message{display:flex;align-items:center;gap:6px;margin-top:6px;font-size:13px;line-height:1.4;padding:4px 8px;border-radius:6px;transition:all .2s ease}.validation-message .validation-icon{display:flex;align-items:center;flex-shrink:0}.validation-message .validation-icon svg{display:block}.validation-message .validation-text{flex:1}.validation-message.validation-error{color:#dc2626;background:#fef2f2;border-left:3px solid #EF4444}.validation-message.validation-warning{color:#d97706;background:#fffbeb;border-left:3px solid #F59E0B}.validation-message.validation-success{color:#059669;background:#f0fdf4;border-left:3px solid #10B981}.validation-message.validation-info{color:#2563eb;background:#eff6ff;border-left:3px solid #3B82F6}.option.recent-option{position:relative;background:#fafbfc}.option.recent-option:after{content:\"\";position:absolute;left:0;top:0;bottom:0;width:3px;background:linear-gradient(to bottom,#2684ff,#0052cc);border-radius:0 3px 3px 0}.option.recent-option:hover:not(.disabled){background:#f4f5f7!important}.option.recent-option.selected{background:#e6f2ff!important}.option.recent-option.selected:hover:not(.disabled){background:#d4e8ff!important}.section-header{padding:8px 12px;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.5px;color:#6b7280;background:#f9fafb;border-bottom:1px solid #E5E7EB;position:sticky;top:0;z-index:2}.virtual-scroll-viewport{width:100%}.virtual-scroll-viewport ::ng-deep .cdk-virtual-scroll-content-wrapper{width:100%}.virtual-scroll-viewport ::ng-deep .cdk-virtual-scroll-spacer{display:none}.virtual-scroll-viewport .option{width:100%;box-sizing:border-box}@keyframes copyFlash{0%,to{background:transparent}50%{background:#2684ff1a}}.select-container.copying{animation:copyFlash .5s ease}.options-list.scrolling{position:relative}.options-list.scrolling:after{content:\"\";position:absolute;bottom:0;left:50%;transform:translate(-50%);width:20px;height:20px;border:2px solid #E5E7EB;border-top-color:#2684ff;border-radius:50%;animation:spin .8s linear infinite}.option[title]{cursor:help}.option.highlighted{position:relative}.option.highlighted:before{content:\"\";position:absolute;left:0;top:0;bottom:0;width:2px;background:#2684ff}@keyframes typeAheadPulse{0%,to{opacity:1}50%{opacity:.7}}.options-list.type-ahead-active .option.highlighted{animation:typeAheadPulse 1s ease-in-out}.select-container:focus-visible{outline:2px solid #2684FF;outline-offset:2px}@media (prefers-contrast: high){.select-trigger{border-width:2px}.option.highlighted{outline:2px solid currentColor;outline-offset:-2px}.validation-message{border-width:2px}}@media (prefers-reduced-motion: reduce){.select-trigger,.option,.tag,.dropdown,.validation-message{transition:none!important;animation:none!important}}.select-container.dark-mode{--ps-bg: #1f2937;--ps-bg-hover: #374151;--ps-border: #4b5563;--ps-text: #f3f4f6;--ps-text-secondary: #d1d5db;--ps-placeholder: #9ca3af;--ps-disabled-bg: #111827;--ps-disabled-text: #6b7280;--ps-shadow: 0 4px 6px rgba(0, 0, 0, .3)}.select-container.dark-mode .select-trigger{background:var(--ps-bg);border-color:var(--ps-border);color:var(--ps-text)}.select-container.dark-mode .select-trigger:hover:not(.disabled){background:var(--ps-bg-hover);border-color:#6b7280}.select-container.dark-mode .menu{background:var(--ps-bg);border-color:var(--ps-border);box-shadow:var(--ps-shadow)}.select-container.dark-mode .option{color:var(--ps-text)}.select-container.dark-mode .option:hover:not(.disabled){background:var(--ps-bg-hover)}.select-container.dark-mode .option.highlighted{background:var(--ps-bg-hover)}.select-container.dark-mode .option.selected{background:#3b82f633}.select-container.dark-mode .tag{background:var(--ps-bg-hover);color:var(--ps-text);border-color:var(--ps-border)}.select-container.dark-mode .empty-state,.select-container.dark-mode .min-search-message{color:var(--ps-text-secondary)}.select-container.dark-mode .group-label{background:var(--ps-bg);color:var(--ps-text-secondary);border-bottom-color:var(--ps-border)}.skeleton-loader{padding:8px 0}.skeleton-loader .skeleton-item{padding:8px 12px;display:flex;flex-direction:column;gap:6px}.skeleton-loader .skeleton-line{background:linear-gradient(90deg,#e5e7eb,#f3f4f6,#e5e7eb);background-size:200% 100%;animation:skeleton-loading 1.5s infinite;border-radius:4px;height:12px}.skeleton-loader .skeleton-line.skeleton-title{width:80%;height:14px}.skeleton-loader .skeleton-line.skeleton-subtitle{width:60%;height:10px}@keyframes skeleton-loading{0%{background-position:200% 0}to{background-position:-200% 0}}.select-container.dark-mode .skeleton-line{background:linear-gradient(90deg,#374151,#4b5563,#374151);background-size:200% 100%}.select-container.compact .select-trigger{min-height:32px;padding:4px 8px;gap:4px}.select-container.compact .tag{padding:2px 6px;font-size:.75rem;gap:4px;height:24px}.select-container.compact .tag .tag-label{font-size:.75rem}.select-container.compact .tag-remove{width:12px;height:12px}.select-container.compact .tag-remove svg{width:10px;height:10px}.select-container.compact .option{min-height:32px;padding:6px 10px;font-size:.875rem}.select-container.compact .menu{max-height:250px}.select-container.compact .select-all-container{padding:6px 10px}.select-container.compact .group-label{padding:6px 10px;font-size:.75rem}.select-container.compact .empty-state,.select-container.compact .min-search-message{padding:12px;font-size:.875rem}.select-container.compact .search-input input{font-size:.875rem;padding:4px 0}.select-container.compact .indicators{gap:4px}.select-container.compact .indicators .indicator{width:14px;height:14px}.select-container.compact .indicators .indicator svg{width:12px;height:12px}.option-checkbox{display:flex;align-items:center;justify-content:center;width:20px;height:20px;flex-shrink:0;margin-right:8px;transition:all .2s ease}.option-checkbox.checkbox-default{border:2px solid #d1d5db;border-radius:4px;background:#fff}.option-checkbox.checkbox-default svg{color:#10b981}.option-checkbox.checkbox-filled{background:var(--ps-primary);border-radius:4px;border:none}.option-checkbox.checkbox-filled svg{color:#fff}.option-checkbox.checkbox-outlined{border:2px solid var(--ps-primary);border-radius:4px;background:transparent}.option-checkbox.checkbox-outlined svg{color:var(--ps-primary)}.option-checkbox .checkbox-empty{width:12px;height:12px;border-radius:2px;background:transparent}.option.with-checkbox{display:flex;align-items:center;gap:10px}.option.with-checkbox.checkbox-right{flex-direction:row-reverse}.option.with-checkbox.checkbox-right .option-checkbox{margin-right:0;margin-left:8px}.select-container.dark-mode .option-checkbox.checkbox-default{border-color:#6b7280;background:#374151}.bulk-actions{padding:12px;background:#f9fafb;border-bottom:1px solid #e5e7eb;display:flex;align-items:center;gap:12px;border-radius:8px 8px 0 0;flex-wrap:wrap}.bulk-actions.position-above{border-bottom:1px solid #e5e7eb;border-radius:8px 8px 0 0;order:-1}.bulk-actions.position-below{border-top:1px solid #e5e7eb;border-bottom:none;border-radius:0 0 8px 8px;order:99}.bulk-actions.position-float{position:sticky;bottom:0;background:#fffffff2;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);z-index:10;border-top:1px solid #e5e7eb;border-bottom:none}.bulk-actions .bulk-label{font-weight:600;color:#4b5563;font-size:.875rem;white-space:nowrap}.bulk-actions .bulk-buttons{display:flex;gap:8px;flex-wrap:wrap}.bulk-actions .bulk-action-btn{padding:6px 12px;background:#fff;border:1px solid #d1d5db;border-radius:6px;cursor:pointer;display:flex;align-items:center;gap:6px;font-size:.875rem;font-weight:500;color:#374151;transition:all .2s ease;white-space:nowrap}.bulk-actions .bulk-action-btn:hover:not(:disabled){background:#f3f4f6;border-color:#9ca3af;transform:translateY(-1px);box-shadow:0 2px 4px #0000001a}.bulk-actions .bulk-action-btn:active:not(:disabled){transform:translateY(0);box-shadow:none}.bulk-actions .bulk-action-btn:disabled{opacity:.5;cursor:not-allowed}.bulk-actions .bulk-action-btn .action-icon{width:16px;height:16px;object-fit:contain}.select-container.dark-mode .bulk-actions{background:#1f2937;border-color:#4b5563}.select-container.dark-mode .bulk-actions.position-float{background:#1f2937f2}.select-container.dark-mode .bulk-actions .bulk-label{color:#d1d5db}.select-container.dark-mode .bulk-actions .bulk-action-btn{background:#374151;border-color:#4b5563;color:#f3f4f6}.select-container.dark-mode .bulk-actions .bulk-action-btn:hover:not(:disabled){background:#4b5563;border-color:#6b7280}.select-container.compact .bulk-actions{padding:8px;gap:8px}.select-container.compact .bulk-actions .bulk-label{font-size:.75rem}.select-container.compact .bulk-actions .bulk-action-btn{padding:4px 8px;font-size:.75rem}.select-container.compact .bulk-actions .bulk-action-btn .action-icon{width:14px;height:14px}.select-container.compact.dark-mode .skeleton-line{height:10px}.select-container.compact.dark-mode .skeleton-line.skeleton-title{height:12px}.select-container.compact.dark-mode .bulk-actions{padding:6px 8px}.select-container.compact.dark-mode .option-checkbox{width:16px;height:16px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i3.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i4.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i4.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: ClickOutsideDirective, selector: "[clickOutside]", outputs: ["clickOutside"] }, { kind: "ngmodule", type: ScrollingModule }, { kind: "directive", type: i5.CdkFixedSizeVirtualScroll, selector: "cdk-virtual-scroll-viewport[itemSize]", inputs: ["itemSize", "minBufferPx", "maxBufferPx"] }, { kind: "component", type: i5.CdkVirtualScrollViewport, selector: "cdk-virtual-scroll-viewport", inputs: ["orientation", "appendOnly"], outputs: ["scrolledIndexChange"] }, { kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i6.CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: i6.CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: i6.CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }, { kind: "directive", type: i6.CdkDragPlaceholder, selector: "ng-template[cdkDragPlaceholder]", inputs: ["data"] }, { kind: "pipe", type: i3.KeyValuePipe, name: "keyvalue" }], animations: selectAnimations });
1153
1495
  }
1154
1496
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: PerfectSelectComponent, decorators: [{
1155
1497
  type: Component,
1156
- args: [{ selector: 'ng-perfect-select', standalone: true, imports: [CommonModule, FormsModule, ClickOutsideDirective, ScrollingModule, DragDropModule], animations: selectAnimations, providers: [{
1498
+ args: [{ selector: 'ng-perfect-select', standalone: true, imports: [
1499
+ CommonModule,
1500
+ FormsModule,
1501
+ ClickOutsideDirective,
1502
+ ScrollingModule,
1503
+ DragDropModule
1504
+ ], animations: selectAnimations, providers: [{
1157
1505
  provide: NG_VALUE_ACCESSOR,
1158
1506
  useExisting: forwardRef(() => PerfectSelectComponent),
1159
1507
  multi: true
1160
- }], template: "<div\n #selectContainer\n class=\"select-container {{selectSize}} {{containerSize}} theme-{{theme}} {{getValidationClass()}}\"\n [class.disabled]=\"isDisabled\"\n [class.rtl]=\"isRtl\"\n [class.has-validation]=\"validationState !== 'default'\"\n (clickOutside)=\"onClickOutside()\"\n role=\"combobox\"\n tabindex=\"0\"\n [attr.aria-controls]=\"'options-list'\"\n [attr.aria-expanded]=\"isOpen()\"\n [attr.aria-invalid]=\"validationState === 'error'\"\n (focus)=\"openMenuOnFocus && !isOpen() && toggleDropdown(); focus.emit()\"\n [style]=\"customStyles.container || ''\"\n>\n <!-- Main Select Trigger -->\n <div\n class=\"select-trigger\"\n [class.open]=\"isOpen()\"\n [class.focused]=\"isOpen()\"\n (click)=\"openMenuOnClick && toggleDropdown()\"\n [attr.tabindex]=\"isDisabled ? -1 : 0\"\n role=\"button\"\n aria-haspopup=\"listbox\"\n [attr.aria-expanded]=\"isOpen()\"\n [attr.aria-label]=\"placeholder\"\n >\n <!-- Selected Value Display -->\n <div class=\"select-value\">\n <!-- Multi-select Tags -->\n @if (isMulti && selectedOptions().length > 0) {\n <!-- v2.1.0: Drag-drop enabled tags -->\n @if (enableDragDrop) {\n <div class=\"tags\" cdkDropList cdkDropListOrientation=\"horizontal\" (cdkDropListDropped)=\"onTagsReorder($event)\" [class.dragging]=\"isDragging()\">\n @for (option of visibleTags(); track trackByValue($index, option)) {\n <span class=\"tag\" [class.disabled]=\"isDisabled\" cdkDrag (cdkDragStarted)=\"onDragStart()\" (cdkDragEnded)=\"onDragEnd()\" [@tag]>\n <div class=\"drag-handle\" cdkDragHandle>\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <path d=\"M9 3h2v2H9V3zm0 4h2v2H9V7zm0 4h2v2H9v-2zm0 4h2v2H9v-2zm0 4h2v2H9v-2zm4-16h2v2h-2V3zm0 4h2v2h-2V7zm0 4h2v2h-2v-2zm0 4h2v2h-2v-2zm0 4h2v2h-2v-2z\"/>\n </svg>\n </div>\n <span class=\"tag-label\">{{getOptionLabel(option)}}</span>\n @if (!isDisabled) {\n <button\n class=\"tag-remove\"\n (click)=\"removeOption(option, $event)\"\n [attr.aria-label]=\"'Remove ' + getOptionLabel(option)\"\n type=\"button\"\n >\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 20 20\">\n <path d=\"M14.348 14.849c-0.469 0.469-1.229 0.469-1.697 0l-2.651-3.030-2.651 3.029c-0.469 0.469-1.229 0.469-1.697 0-0.469-0.469-0.469-1.229 0-1.697l2.758-3.15-2.759-3.152c-0.469-0.469-0.469-1.228 0-1.697s1.228-0.469 1.697 0l2.652 3.031 2.651-3.031c0.469-0.469 1.228-0.469 1.697 0s0.469 1.229 0 1.697l-2.758 3.152 2.758 3.15c0.469 0.469 0.469 1.229 0 1.698z\"></path>\n </svg>\n </button>\n }\n <div *cdkDragPlaceholder class=\"drag-placeholder\">{{dragDropPlaceholder}}</div>\n </span>\n }\n </div>\n } @else {\n <div class=\"tags\">\n @for (option of visibleTags(); track trackByValue($index, option)) {\n <span class=\"tag\" [class.disabled]=\"isDisabled\" [@tag]>\n <span class=\"tag-label\">{{getOptionLabel(option)}}</span>\n @if (!isDisabled) {\n <button\n class=\"tag-remove\"\n (click)=\"removeOption(option, $event)\"\n [attr.aria-label]=\"'Remove ' + getOptionLabel(option)\"\n type=\"button\"\n >\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 20 20\">\n <path d=\"M14.348 14.849c-0.469 0.469-1.229 0.469-1.697 0l-2.651-3.030-2.651 3.029c-0.469 0.469-1.229 0.469-1.697 0-0.469-0.469-0.469-1.229 0-1.697l2.758-3.15-2.759-3.152c-0.469-0.469-0.469-1.228 0-1.697s1.228-0.469 1.697 0l2.652 3.031 2.651-3.031c0.469-0.469 1.228-0.469 1.697 0s0.469 1.229 0 1.697l-2.758 3.152 2.758 3.15c0.469 0.469 0.469 1.229 0 1.698z\"></path>\n </svg>\n </button>\n }\n </span>\n }\n </div>\n }\n\n <!-- v2.2.0: Tag overflow indicator -->\n @if (hiddenTagsCount() > 0) {\n @if (collapsibleTags) {\n <button\n class=\"tags-toggle\"\n (click)=\"toggleTagsExpanded()\"\n [attr.aria-label]=\"tagsExpanded() ? showLessTagsText : showAllTagsText\"\n type=\"button\"\n >\n {{ tagsExpanded() ? showLessTagsText : showAllTagsText }}\n </button>\n } @else {\n <span class=\"tags-overflow-indicator\">\n {{ getMoreTagsText() }}\n </span>\n }\n }\n } @else {\n <!-- Single Select or Placeholder -->\n <span class=\"placeholder\" [class.has-value]=\"selectedOptions().length > 0\">\n {{displayText()}}\n </span>\n }\n </div>\n\n <!-- Actions (Clear, Loading, Arrow) -->\n <div class=\"select-actions\">\n @if (isLoading || isLoadingAsync()) {\n <div class=\"spinner\"></div>\n }\n @if (isClearable && selectedOptions().length > 0 && !isDisabled && !isLoading && !isLoadingAsync()) {\n <button\n class=\"clear-button\"\n (click)=\"clearSelection($event)\"\n aria-label=\"Clear selection\"\n type=\"button\"\n >\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 20 20\">\n <path d=\"M14.348 14.849c-0.469 0.469-1.229 0.469-1.697 0l-2.651-3.030-2.651 3.029c-0.469 0.469-1.229 0.469-1.697 0-0.469-0.469-0.469-1.229 0-1.697l2.758-3.15-2.759-3.152c-0.469-0.469-0.469-1.228 0-1.697s1.228-0.469 1.697 0l2.652 3.031 2.651-3.031c0.469-0.469 1.228-0.469 1.697 0s0.469 1.229 0 1.697l-2.758 3.152 2.758 3.15c0.469 0.469 0.469 1.229 0 1.698z\"></path>\n </svg>\n </button>\n }\n <span class=\"separator\"></span>\n <span class=\"arrow\" [class.open]=\"isOpen()\">\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 20 20\">\n <path d=\"M4.516 7.548c0.436-0.446 1.043-0.481 1.576 0l3.908 3.747 3.908-3.747c0.533-0.481 1.141-0.446 1.574 0 0.436 0.445 0.408 1.197 0 1.615-0.406 0.418-4.695 4.502-4.695 4.502-0.217 0.223-0.502 0.335-0.787 0.335s-0.57-0.112-0.789-0.335c0 0-4.287-4.084-4.695-4.502s-0.436-1.17 0-1.615z\"></path>\n </svg>\n </span>\n </div>\n </div>\n\n <!-- Dropdown Menu -->\n @if (isOpen()) {\n <div\n #menuRef\n class=\"dropdown {{menuPlacement}}\"\n [class.fixed]=\"menuPosition === 'fixed'\"\n [style.max-height]=\"maxHeight\"\n role=\"listbox\"\n [attr.aria-multiselectable]=\"isMulti\"\n [@dropdown]\n >\n <!-- Search Input -->\n @if (isSearchable) {\n <div class=\"search-container\">\n <input\n #searchInput\n type=\"text\"\n class=\"search-input\"\n placeholder=\"Search...\"\n [value]=\"searchTerm()\"\n (input)=\"onSearchChange($any($event.target).value)\"\n (click)=\"$event.stopPropagation()\"\n aria-label=\"Search options\"\n aria-autocomplete=\"list\"\n />\n </div>\n }\n\n <!-- Options List -->\n <div\n class=\"options-list\"\n id=\"options-list\"\n (scroll)=\"onOptionsScroll($event)\"\n >\n <!-- Max Selection Message -->\n @if (isMaxSelectionReached()) {\n <div class=\"info-message max-selection\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 20 20\" fill=\"currentColor\">\n <path d=\"M10 0C4.477 0 0 4.477 0 10s4.477 10 10 10 10-4.477 10-10S15.523 0 10 0zm1 15H9v-2h2v2zm0-4H9V5h2v6z\"/>\n </svg>\n {{maxSelectedMessage}}\n </div>\n }\n\n <!-- Min Search Length Message -->\n @if (showMinSearchMessage()) {\n <div class=\"info-message min-search\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 20 20\" fill=\"currentColor\">\n <path d=\"M10 0C4.477 0 0 4.477 0 10s4.477 10 10 10 10-4.477 10-10S15.523 0 10 0zm1 15H9v-2h2v2zm0-4H9V5h2v6z\"/>\n </svg>\n {{minSearchMessage}} ({{searchTerm().length}}/{{minSearchLength}})\n </div>\n }\n\n @if (isLoadingAsync()) {\n <div class=\"loading-message\">{{loadingMessage()}}</div>\n } @else if (displayOptions().length === 0 && !showMinSearchMessage()) {\n <div class=\"no-options\">\n {{searchTerm() ? emptySearchText : emptyStateText}}\n </div>\n } @else if (!showMinSearchMessage()) {\n <!-- Select All (Multi-select only) -->\n @if (isMulti && showSelectAll && !searchTerm()) {\n <div class=\"select-all-container\">\n <button\n class=\"select-all-button\"\n (click)=\"allOptionsSelected() ? deselectAll() : selectAll()\"\n type=\"button\"\n >\n <input\n type=\"checkbox\"\n [checked]=\"allOptionsSelected()\"\n [indeterminate]=\"someOptionsSelected()\"\n tabindex=\"-1\"\n aria-hidden=\"true\"\n readonly\n />\n <span class=\"select-all-text\">\n {{allOptionsSelected() ? deselectAllText : selectAllText}}\n </span>\n <span class=\"select-all-count\">\n ({{selectedOptions().length}}/{{getEnabledOptionsCount()}})\n </span>\n </button>\n </div>\n }\n\n <!-- Grouped Options -->\n @if (isGrouped && groupedOptions()) {\n @for (group of groupedOptions() | keyvalue; track trackByGroup($index, [$any(group.key), $any(group.value)])) {\n <div class=\"option-group\">\n <div class=\"option-group-label\">{{group.key}}</div>\n @for (option of $any(group.value); track trackByValue($index, option); let idx = $index) {\n <div\n class=\"option\"\n [class.selected]=\"isSelected(option)\"\n [class.highlighted]=\"idx === highlightedIndex()\"\n [class.disabled]=\"isOptionDisabled(option) || (isMaxSelectionReached() && !isSelected(option))\"\n [class.hidden]=\"hideSelectedOptions && isSelected(option)\"\n [class.create-option]=\"option.__isCreate__\"\n (click)=\"selectOption(option)\"\n (keydown)=\"$event.key === 'Enter' && selectOption(option)\"\n role=\"option\"\n [attr.aria-selected]=\"isSelected(option)\"\n [attr.aria-disabled]=\"isOptionDisabled(option) || (isMaxSelectionReached() && !isSelected(option))\"\n tabindex=\"-1\"\n >\n <!-- Checkbox for multi-select -->\n @if (isMulti) {\n <input\n type=\"checkbox\"\n [checked]=\"isSelected(option)\"\n tabindex=\"-1\"\n aria-hidden=\"true\"\n readonly\n />\n }\n\n <!-- Option Icon -->\n @if (showOptionIcons && option.icon) {\n <div class=\"option-icon\">\n @if (option.icon.includes('<')) {\n <div [innerHTML]=\"sanitizeHtml(option.icon)\"></div>\n } @else {\n <img [src]=\"option.icon\" [alt]=\"getOptionLabel(option)\" />\n }\n </div>\n }\n\n <!-- Option Content -->\n <div class=\"option-content\">\n <div class=\"option-label\" [innerHTML]=\"highlightSearchTerm(getOptionLabel(option))\"></div>\n @if (option.description) {\n <div class=\"option-description\">{{option.description}}</div>\n }\n </div>\n\n <!-- Option Badge -->\n @if (showOptionBadges && option.badge) {\n <span\n class=\"option-badge\"\n [style.background-color]=\"option.badgeColor || currentTheme().primary\"\n >\n {{option.badge}}\n </span>\n }\n\n <!-- v2.1.0: Pin button -->\n @if (enablePinning) {\n <button\n class=\"pin-button\"\n [class.pinned]=\"isPinned(option)\"\n (click)=\"togglePin(option, $event)\"\n [attr.aria-label]=\"isPinned(option) ? 'Unpin option' : 'Pin option'\"\n type=\"button\"\n >\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n @if (isPinned(option)) {\n <path d=\"M16,12V4H17V2H7V4H8V12L6,14V16H11.2V22H12.8V16H18V14L16,12Z\"/>\n } @else {\n <path d=\"M16,12V4H17V2H7V4H8V12L6,14V16H11.2V22H12.8V16H18V14L16,12M8.8,14L10,12.8V4H14V12.8L15.2,14H8.8Z\"/>\n }\n </svg>\n </button>\n }\n\n <!-- Check Icon for selected -->\n @if (!isMulti && isSelected(option)) {\n <svg class=\"check-icon\" width=\"16\" height=\"16\" viewBox=\"0 0 20 20\">\n <path d=\"M7.629,14.566c0.125,0.125,0.291,0.188,0.456,0.188c0.164,0,0.329-0.062,0.456-0.188l8.219-8.221c0.252-0.252,0.252-0.659,0-0.911c-0.252-0.252-0.659-0.252-0.911,0l-7.764,7.763L4.152,9.267c-0.252-0.251-0.66-0.251-0.911,0c-0.252,0.252-0.252,0.66,0,0.911L7.629,14.566z\"></path>\n </svg>\n }\n </div>\n }\n </div>\n }\n } @else {\n <!-- v1.2.0: Recent Selections Header -->\n @if (showRecentSelections && !searchTerm() && recentSelections().length > 0) {\n <div class=\"section-header\">{{recentSelectionsLabel}}</div>\n }\n\n <!-- Regular (Ungrouped) Options - Virtual Scroll or Standard -->\n @if (enableVirtualScroll) {\n <!-- Virtual Scroll Mode -->\n <cdk-virtual-scroll-viewport\n [itemSize]=\"virtualScrollItemSize\"\n [minBufferPx]=\"virtualScrollMinBufferPx\"\n [maxBufferPx]=\"virtualScrollMaxBufferPx\"\n [style.height.px]=\"maxHeight\"\n class=\"virtual-scroll-viewport\"\n >\n @for (option of (showRecentSelections ? displayOptionsWithRecent() : displayOptions()); track trackByValue($index, option); let idx = $index) {\n <div\n class=\"option\"\n [class.selected]=\"isSelected(option)\"\n [class.highlighted]=\"idx === highlightedIndex()\"\n [class.disabled]=\"isOptionDisabled(option) || (isMaxSelectionReached() && !isSelected(option))\"\n [class.hidden]=\"hideSelectedOptions && isSelected(option)\"\n [class.create-option]=\"option.__isCreate__\"\n [class.recent-option]=\"option.__isRecent__\"\n (click)=\"selectOption(option)\"\n (keydown)=\"$event.key === 'Enter' && selectOption(option)\"\n (mouseenter)=\"showTooltip(option, idx)\"\n (mouseleave)=\"hideTooltip()\"\n role=\"option\"\n [attr.aria-selected]=\"isSelected(option)\"\n [attr.aria-disabled]=\"isOptionDisabled(option) || (isMaxSelectionReached() && !isSelected(option))\"\n [attr.title]=\"showTooltips ? getTooltipContent(option) : null\"\n tabindex=\"-1\"\n >\n <!-- Custom Template or Default Rendering -->\n @if (optionTemplate) {\n <ng-container\n *ngTemplateOutlet=\"optionTemplate; context: { $implicit: option, index: idx, selected: isSelected(option) }\"\n ></ng-container>\n } @else {\n <!-- Default Option Content (same as before) -->\n @if (isMulti) {\n <input\n type=\"checkbox\"\n [checked]=\"isSelected(option)\"\n tabindex=\"-1\"\n aria-hidden=\"true\"\n readonly\n />\n }\n\n @if (showOptionIcons && option.icon) {\n <div class=\"option-icon\">\n @if (option.icon.includes('<')) {\n <div [innerHTML]=\"sanitizeHtml(option.icon)\"></div>\n } @else {\n <img [src]=\"option.icon\" [alt]=\"getOptionLabel(option)\" />\n }\n </div>\n }\n\n <div class=\"option-content\">\n <div class=\"option-label\" [innerHTML]=\"highlightSearchTerm(getOptionLabel(option))\"></div>\n @if (option.description) {\n <div class=\"option-description\">{{option.description}}</div>\n }\n </div>\n\n @if (showOptionBadges && option.badge) {\n <span\n class=\"option-badge\"\n [style.background-color]=\"option.badgeColor || currentTheme().primary\"\n >\n {{option.badge}}\n </span>\n }\n\n <!-- v2.1.0: Pin button -->\n @if (enablePinning) {\n <button\n class=\"pin-button\"\n [class.pinned]=\"isPinned(option)\"\n (click)=\"togglePin(option, $event)\"\n [attr.aria-label]=\"isPinned(option) ? 'Unpin option' : 'Pin option'\"\n type=\"button\"\n >\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n @if (isPinned(option)) {\n <path d=\"M16,12V4H17V2H7V4H8V12L6,14V16H11.2V22H12.8V16H18V14L16,12Z\"/>\n } @else {\n <path d=\"M16,12V4H17V2H7V4H8V12L6,14V16H11.2V22H12.8V16H18V14L16,12M8.8,14L10,12.8V4H14V12.8L15.2,14H8.8Z\"/>\n }\n </svg>\n </button>\n }\n\n @if (!isMulti && isSelected(option)) {\n <svg class=\"check-icon\" width=\"16\" height=\"16\" viewBox=\"0 0 20 20\">\n <path d=\"M7.629,14.566c0.125,0.125,0.291,0.188,0.456,0.188c0.164,0,0.329-0.062,0.456-0.188l8.219-8.221c0.252-0.252,0.252-0.659,0-0.911c-0.252-0.252-0.659-0.252-0.911,0l-7.764,7.763L4.152,9.267c-0.252-0.251-0.66-0.251-0.911,0c-0.252,0.252-0.252,0.66,0,0.911L7.629,14.566z\"></path>\n </svg>\n }\n }\n </div>\n }\n </cdk-virtual-scroll-viewport>\n } @else {\n <!-- Standard Rendering (Non-Virtual Scroll) -->\n @for (option of (showRecentSelections ? displayOptionsWithRecent() : displayOptions()); track trackByValue($index, option); let idx = $index) {\n <div\n class=\"option\"\n [class.selected]=\"isSelected(option)\"\n [class.highlighted]=\"idx === highlightedIndex()\"\n [class.disabled]=\"isOptionDisabled(option) || (isMaxSelectionReached() && !isSelected(option))\"\n [class.hidden]=\"hideSelectedOptions && isSelected(option)\"\n [class.create-option]=\"option.__isCreate__\"\n [class.recent-option]=\"option.__isRecent__\"\n (click)=\"selectOption(option)\"\n (keydown)=\"$event.key === 'Enter' && selectOption(option)\"\n (mouseenter)=\"showTooltip(option, idx)\"\n (mouseleave)=\"hideTooltip()\"\n role=\"option\"\n [attr.aria-selected]=\"isSelected(option)\"\n [attr.aria-disabled]=\"isOptionDisabled(option) || (isMaxSelectionReached() && !isSelected(option))\"\n [attr.title]=\"showTooltips ? getTooltipContent(option) : null\"\n tabindex=\"-1\"\n >\n <!-- Custom Template or Default Rendering -->\n @if (optionTemplate) {\n <ng-container\n *ngTemplateOutlet=\"optionTemplate; context: { $implicit: option, index: idx, selected: isSelected(option) }\"\n ></ng-container>\n } @else {\n <!-- Default Option Content -->\n @if (isMulti) {\n <input\n type=\"checkbox\"\n [checked]=\"isSelected(option)\"\n tabindex=\"-1\"\n aria-hidden=\"true\"\n readonly\n />\n }\n\n @if (showOptionIcons && option.icon) {\n <div class=\"option-icon\">\n @if (option.icon.includes('<')) {\n <div [innerHTML]=\"sanitizeHtml(option.icon)\"></div>\n } @else {\n <img [src]=\"option.icon\" [alt]=\"getOptionLabel(option)\" />\n }\n </div>\n }\n\n <div class=\"option-content\">\n <div class=\"option-label\" [innerHTML]=\"highlightSearchTerm(getOptionLabel(option))\"></div>\n @if (option.description) {\n <div class=\"option-description\">{{option.description}}</div>\n }\n </div>\n\n @if (showOptionBadges && option.badge) {\n <span\n class=\"option-badge\"\n [style.background-color]=\"option.badgeColor || currentTheme().primary\"\n >\n {{option.badge}}\n </span>\n }\n\n <!-- v2.1.0: Pin button -->\n @if (enablePinning) {\n <button\n class=\"pin-button\"\n [class.pinned]=\"isPinned(option)\"\n (click)=\"togglePin(option, $event)\"\n [attr.aria-label]=\"isPinned(option) ? 'Unpin option' : 'Pin option'\"\n type=\"button\"\n >\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n @if (isPinned(option)) {\n <path d=\"M16,12V4H17V2H7V4H8V12L6,14V16H11.2V22H12.8V16H18V14L16,12Z\"/>\n } @else {\n <path d=\"M16,12V4H17V2H7V4H8V12L6,14V16H11.2V22H12.8V16H18V14L16,12M8.8,14L10,12.8V4H14V12.8L15.2,14H8.8Z\"/>\n }\n </svg>\n </button>\n }\n\n @if (!isMulti && isSelected(option)) {\n <svg class=\"check-icon\" width=\"16\" height=\"16\" viewBox=\"0 0 20 20\">\n <path d=\"M7.629,14.566c0.125,0.125,0.291,0.188,0.456,0.188c0.164,0,0.329-0.062,0.456-0.188l8.219-8.221c0.252-0.252,0.252-0.659,0-0.911c-0.252-0.252-0.659-0.252-0.911,0l-7.764,7.763L4.152,9.267c-0.252-0.251-0.66-0.251-0.911,0c-0.252,0.252-0.252,0.66,0,0.911L7.629,14.566z\"></path>\n </svg>\n }\n }\n </div>\n }\n }\n }\n }\n </div>\n </div>\n }\n\n <!-- v1.2.0: Validation Message -->\n @if (validationMessage && validationState !== 'default') {\n <div class=\"validation-message {{getValidationClass()}}\">\n @if (showValidationIcon) {\n <span class=\"validation-icon\">\n @if (validationState === 'error') {\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 20 20\" fill=\"currentColor\">\n <path d=\"M10 0C4.477 0 0 4.477 0 10s4.477 10 10 10 10-4.477 10-10S15.523 0 10 0zm1 15H9v-2h2v2zm0-4H9V5h2v6z\"/>\n </svg>\n } @else if (validationState === 'warning') {\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 20 20\" fill=\"currentColor\">\n <path d=\"M10 0C4.477 0 0 4.477 0 10s4.477 10 10 10 10-4.477 10-10S15.523 0 10 0zm1 15H9v-2h2v2zm0-4H9V5h2v6z\"/>\n </svg>\n } @else if (validationState === 'success') {\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 20 20\" fill=\"currentColor\">\n <path d=\"M10 0C4.477 0 0 4.477 0 10s4.477 10 10 10 10-4.477 10-10S15.523 0 10 0zm-1.707 13.707l-3.5-3.5 1.414-1.414L9 11.586l5.293-5.293 1.414 1.414-6.5 6.5z\"/>\n </svg>\n } @else if (validationState === 'info') {\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 20 20\" fill=\"currentColor\">\n <path d=\"M10 0C4.477 0 0 4.477 0 10s4.477 10 10 10 10-4.477 10-10S15.523 0 10 0zm1 15H9v-6h2v6zm0-8H9V5h2v2z\"/>\n </svg>\n }\n </span>\n }\n <span class=\"validation-text\">{{validationMessage}}</span>\n </div>\n }\n</div>\n\n<!-- Hidden native select for form compatibility -->\n@if (isMulti) {\n <select [name]=\"name\" [id]=\"id\" multiple style=\"display: none;\" [attr.aria-hidden]=\"true\">\n @for (option of selectedOptions(); track trackByValue($index, option)) {\n <option [value]=\"getOptionValue(option)\" selected>{{getOptionLabel(option)}}</option>\n }\n </select>\n} @else {\n @if (selectedOptions().length > 0) {\n <select [name]=\"name\" [id]=\"id\" style=\"display: none;\" [attr.aria-hidden]=\"true\">\n <option [value]=\"getOptionValue(selectedOptions()[0])\" selected>{{getOptionLabel(selectedOptions()[0])}}</option>\n </select>\n }\n}\n", styles: [":host{display:block;width:100%}.select-container{position:relative;width:100%;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif}.smaller{font-size:11px}.small{font-size:13px}.medium{font-size:14px}.large{font-size:16px}.larger{font-size:18px}.xs .select-trigger{min-height:28px;padding:4px 8px}.sm .select-trigger{min-height:32px;padding:6px 10px}.md .select-trigger{min-height:40px;padding:8px 12px}.lg .select-trigger{min-height:48px;padding:10px 14px}.xl .select-trigger{min-height:56px;padding:12px 16px}.select-container.disabled{opacity:.6;cursor:not-allowed}.select-container.rtl{direction:rtl}.select-trigger{display:flex;align-items:center;justify-content:space-between;padding:8px 12px;background:#fff;border:1.5px solid #D1D5DB;border-radius:10px;cursor:pointer;transition:all .2s cubic-bezier(.4,0,.2,1);min-height:38px;gap:8px;box-shadow:0 1px 2px #0000000d}.select-trigger:hover:not(.disabled){border-color:#9ca3af;box-shadow:0 2px 4px #00000014}.select-trigger:focus,.select-trigger.focused{outline:none;border-color:#2684ff;box-shadow:0 0 0 3px #2684ff1a,0 1px 2px #0000000d}.select-trigger.open{border-color:#2684ff;box-shadow:0 0 0 3px #2684ff1a,0 1px 2px #0000000d}.select-value{flex:1;display:flex;align-items:center;min-width:0;gap:4px}.placeholder{color:#999;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.placeholder.has-value{color:#333}.tags{display:flex;flex-wrap:wrap;gap:4px;flex:1}.tag{display:inline-flex;align-items:center;gap:4px;padding:4px 8px;background:#e6f2ff;color:#0052cc;border-radius:6px;font-size:.875em;font-weight:500;white-space:nowrap;border:1px solid #CCE0FF;box-shadow:0 1px 2px #0000000d}.tag.disabled{background:#f0f0f0;color:#666;border-color:#d9d9d9}.tag-label{line-height:1.2}.tag-remove{background:none;border:none;color:inherit;cursor:pointer;padding:0;display:flex;align-items:center;justify-content:center;border-radius:2px;transition:all .15s}.tag-remove:hover{background:#0052cc26}.tag-remove svg{fill:currentColor}.tags.dragging .tag{cursor:move}.drag-handle{display:flex;align-items:center;justify-content:center;cursor:move;padding:0;margin-left:-4px;opacity:.5;transition:opacity .15s}.drag-handle svg{fill:currentColor}.drag-handle:hover{opacity:.8}.cdk-drag-preview{box-shadow:0 5px 15px #0003;opacity:.8}.cdk-drag-animating{transition:transform .25s cubic-bezier(0,0,.2,1)}.drag-placeholder{background:#0052cc1a;border:2px dashed var(--ps-primary, #0052cc);border-radius:6px;padding:4px 8px;min-height:28px;display:flex;align-items:center;justify-content:center;color:var(--ps-primary, #0052cc);font-size:.75em;opacity:.6}.cdk-drop-list-dragging .cdk-drag{transition:transform .25s cubic-bezier(0,0,.2,1)}.tags-toggle{background:none;border:none;color:var(--ps-primary, #0052cc);cursor:pointer;font-size:.875em;font-weight:500;padding:4px 8px;border-radius:4px;transition:all .15s;white-space:nowrap}.tags-toggle:hover{background:#0052cc1a;text-decoration:underline}.tags-toggle:focus{outline:2px solid var(--ps-primary, #0052cc);outline-offset:2px}.tags-overflow-indicator{display:inline-flex;align-items:center;padding:4px 8px;background:#f3f4f6;color:#6b7280;border-radius:6px;font-size:.875em;font-weight:500;white-space:nowrap;border:1px solid #d1d5db}.option-label mark{background-color:#ffeb3b;color:#000;padding:0 2px;border-radius:2px;font-weight:600}.select-actions{display:flex;align-items:center;gap:4px;flex-shrink:0}.clear-button{background:none;border:none;color:#999;cursor:pointer;padding:4px;display:flex;align-items:center;justify-content:center;border-radius:3px;transition:all .15s}.clear-button:hover{color:#333;background:#f0f0f0}.clear-button svg{fill:currentColor}.separator{width:1px;height:24px;background:#ccc;align-self:stretch}.arrow{color:#999;transition:transform .2s cubic-bezier(.4,0,.2,1);display:flex;align-items:center;padding:4px}.arrow svg{fill:currentColor}.arrow.open{transform:rotate(180deg)}.spinner{width:16px;height:16px;border:2px solid #f3f3f3;border-top:2px solid #2684FF;border-radius:50%;animation:spin .6s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}.dropdown{position:absolute;top:calc(100% + 8px);left:0;right:0;background:#fffffffa;border:1px solid #E5E7EB;border-radius:12px;box-shadow:0 20px 25px -5px #0000001a,0 10px 10px -5px #0000000a,0 0 0 1px #0000000d;z-index:1000;overflow:hidden;backdrop-filter:blur(12px) saturate(180%);-webkit-backdrop-filter:blur(12px) saturate(180%)}.dropdown.fixed{position:fixed}.dropdown.top{top:auto;bottom:calc(100% + 4px)}.search-container{padding:8px;border-bottom:1px solid #f0f0f0;background:#fff}.search-input{width:100%;padding:6px 8px;border:1px solid #cccccc;border-radius:3px;font-size:inherit;outline:none;transition:border-color .15s}.search-input:focus{border-color:#2684ff;box-shadow:0 0 0 1px #2684ff}.options-list{max-height:inherit;overflow-y:auto;overflow-x:hidden;padding:4px 0}.options-list::-webkit-scrollbar{width:8px}.options-list::-webkit-scrollbar-track{background:transparent}.options-list::-webkit-scrollbar-thumb{background:#d9d9d9;border-radius:4px}.options-list::-webkit-scrollbar-thumb:hover{background:#b3b3b3}.select-all-container{padding:8px;border-bottom:1px solid #E5E7EB;background:#f9fafb}.select-all-button{width:100%;display:flex;align-items:center;gap:8px;padding:6px 8px;background:#fff;border:1px solid #D1D5DB;border-radius:6px;cursor:pointer;transition:all .15s;font-size:inherit;color:inherit;font-family:inherit}.select-all-button:hover{background:#f3f4f6;border-color:#9ca3af}.select-all-button input[type=checkbox]{cursor:pointer;width:16px;height:16px;margin:0;accent-color:#2684FF}.select-all-text{flex:1;text-align:left;font-weight:500}.select-all-count{color:#6b7280;font-size:.9em}.option-group{margin:4px 0}.option-group-label{padding:8px 12px 4px;font-size:.75em;font-weight:600;color:#6b7280;text-transform:uppercase;letter-spacing:.05em;background:#f9fafb;border-bottom:1px solid #E5E7EB;position:sticky;top:0;z-index:1}.option-group .option{padding-left:20px}.option{display:flex;align-items:center;gap:10px;padding:10px 12px;cursor:pointer;transition:all .15s cubic-bezier(.4,0,.2,1);position:relative;min-height:40px}.option-content{flex:1;display:flex;flex-direction:column;gap:2px;min-width:0}.option-icon{display:flex;align-items:center;justify-content:center;width:32px;height:32px;flex-shrink:0;border-radius:6px;overflow:hidden;background:#f3f4f6}.option-icon img{width:100%;height:100%;object-fit:cover}.option-badge{padding:2px 8px;border-radius:12px;font-size:.75em;font-weight:500;color:#374151;background:#e5e7eb;flex-shrink:0;white-space:nowrap}.option:hover:not(.disabled):not(.create-option){background:#deebff}.option.highlighted{background:#deebff}.option.selected:not(.create-option){background:#e6f2ff;font-weight:500}.option.disabled{opacity:.5;cursor:not-allowed;background:transparent!important}.option.hidden{display:none}.option.create-option{color:#2684ff;font-weight:500;background:#f0f6ff}.option.create-option:hover{background:#e6f2ff}.option input[type=checkbox]{cursor:pointer;width:16px;height:16px;margin:0;accent-color:#2684FF}.option-label{font-weight:500;color:#1f2937;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;line-height:1.4}.option-description{font-size:.875em;color:#6b7280;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;line-height:1.3}.check-icon{color:#2684ff;display:flex;align-items:center;margin-left:auto}.check-icon svg{fill:currentColor}.pin-button{background:none;border:none;color:#999;cursor:pointer;padding:4px;display:flex;align-items:center;justify-content:center;border-radius:3px;transition:all .15s;margin-left:auto;opacity:.5}.pin-button svg{fill:currentColor}.pin-button:hover{opacity:1;background:#0000000d}.pin-button.pinned{color:var(--ps-primary, #0052cc);opacity:1}.option.pinned-option{background:#0052cc0d;border-left:3px solid var(--ps-primary, #0052cc);padding-left:9px}.no-options,.loading-message{padding:16px 12px;text-align:center;color:#999;font-size:.95em}.info-message{padding:12px;margin:8px;border-radius:6px;font-size:.9em;display:flex;align-items:center;gap:8px}.info-message.max-selection{background:#fff4e5;color:#f57c00;border:1px solid #FFE0B2}.info-message.min-search{background:#e3f2fd;color:#1976d2;border:1px solid #BBDEFB}.info-message svg{flex-shrink:0}.rtl .select-actions,.rtl .tags,.rtl .option{flex-direction:row-reverse}.theme-blue .select-trigger:focus,.theme-blue .select-trigger.focused,.theme-blue .select-trigger.open{border-color:#2684ff;box-shadow:0 0 0 1px #2684ff}.theme-blue .option:hover:not(.disabled):not(.create-option),.theme-blue .option.highlighted{background:#deebff}.theme-blue .option.selected:not(.create-option){background:#e6f2ff}.theme-blue .tag{background:#e6f2ff;color:#0052cc;border-color:#cce0ff}.theme-blue .check-icon,.theme-blue .option.create-option{color:#2684ff}.theme-blue .spinner{border-top-color:#2684ff}.theme-blue .search-input:focus{border-color:#2684ff;box-shadow:0 0 0 1px #2684ff}.theme-purple .select-trigger:focus,.theme-purple .select-trigger.focused,.theme-purple .select-trigger.open{border-color:#9333ea;box-shadow:0 0 0 1px #9333ea}.theme-purple .option:hover:not(.disabled):not(.create-option),.theme-purple .option.highlighted{background:#f3e8ff}.theme-purple .option.selected:not(.create-option){background:#faf5ff}.theme-purple .tag{background:#faf5ff;color:#7e22ce;border-color:#e9d5ff}.theme-purple .check-icon,.theme-purple .option.create-option{color:#9333ea}.theme-purple .spinner{border-top-color:#9333ea}.theme-purple .search-input:focus{border-color:#9333ea;box-shadow:0 0 0 1px #9333ea}.theme-green .select-trigger:focus,.theme-green .select-trigger.focused,.theme-green .select-trigger.open{border-color:#10b981;box-shadow:0 0 0 1px #10b981}.theme-green .option:hover:not(.disabled):not(.create-option),.theme-green .option.highlighted{background:#d1fae5}.theme-green .option.selected:not(.create-option){background:#ecfdf5}.theme-green .tag{background:#ecfdf5;color:#059669;border-color:#a7f3d0}.theme-green .check-icon,.theme-green .option.create-option{color:#10b981}.theme-green .spinner{border-top-color:#10b981}.theme-green .search-input:focus{border-color:#10b981;box-shadow:0 0 0 1px #10b981}.theme-red .select-trigger:focus,.theme-red .select-trigger.focused,.theme-red .select-trigger.open{border-color:#ef4444;box-shadow:0 0 0 1px #ef4444}.theme-red .option:hover:not(.disabled):not(.create-option),.theme-red .option.highlighted{background:#fee2e2}.theme-red .option.selected:not(.create-option){background:#fef2f2}.theme-red .tag{background:#fef2f2;color:#dc2626;border-color:#fecaca}.theme-red .check-icon,.theme-red .option.create-option{color:#ef4444}.theme-red .spinner{border-top-color:#ef4444}.theme-red .search-input:focus{border-color:#ef4444;box-shadow:0 0 0 1px #ef4444}.theme-orange .select-trigger:focus,.theme-orange .select-trigger.focused,.theme-orange .select-trigger.open{border-color:#f97316;box-shadow:0 0 0 1px #f97316}.theme-orange .option:hover:not(.disabled):not(.create-option),.theme-orange .option.highlighted{background:#ffedd5}.theme-orange .option.selected:not(.create-option){background:#fff7ed}.theme-orange .tag{background:#fff7ed;color:#ea580c;border-color:#fed7aa}.theme-orange .check-icon,.theme-orange .option.create-option{color:#f97316}.theme-orange .spinner{border-top-color:#f97316}.theme-orange .search-input:focus{border-color:#f97316;box-shadow:0 0 0 1px #f97316}.theme-pink .select-trigger:focus,.theme-pink .select-trigger.focused,.theme-pink .select-trigger.open{border-color:#ec4899;box-shadow:0 0 0 1px #ec4899}.theme-pink .option:hover:not(.disabled):not(.create-option),.theme-pink .option.highlighted{background:#fce7f3}.theme-pink .option.selected:not(.create-option){background:#fdf2f8}.theme-pink .tag{background:#fdf2f8;color:#db2777;border-color:#fbcfe8}.theme-pink .check-icon,.theme-pink .option.create-option{color:#ec4899}.theme-pink .spinner{border-top-color:#ec4899}.theme-pink .search-input:focus{border-color:#ec4899;box-shadow:0 0 0 1px #ec4899}.theme-dark .select-trigger:focus,.theme-dark .select-trigger.focused,.theme-dark .select-trigger.open{border-color:#1f2937;box-shadow:0 0 0 1px #1f2937}.theme-dark .option:hover:not(.disabled):not(.create-option),.theme-dark .option.highlighted{background:#e5e7eb}.theme-dark .option.selected:not(.create-option){background:#f3f4f6}.theme-dark .tag{background:#f3f4f6;color:#111827;border-color:#d1d5db}.theme-dark .check-icon,.theme-dark .option.create-option{color:#1f2937}.theme-dark .spinner{border-top-color:#1f2937}.theme-dark .search-input:focus{border-color:#1f2937;box-shadow:0 0 0 1px #1f2937}.select-container.has-validation .select-trigger{transition:all .2s cubic-bezier(.4,0,.2,1)}.select-container.validation-error .select-trigger{border-color:#ef4444!important}.select-container.validation-error .select-trigger:focus,.select-container.validation-error .select-trigger.focused,.select-container.validation-error .select-trigger.open{border-color:#ef4444!important;box-shadow:0 0 0 3px #ef44441a,0 1px 2px #0000000d}.select-container.validation-warning .select-trigger{border-color:#f59e0b!important}.select-container.validation-warning .select-trigger:focus,.select-container.validation-warning .select-trigger.focused,.select-container.validation-warning .select-trigger.open{border-color:#f59e0b!important;box-shadow:0 0 0 3px #f59e0b1a,0 1px 2px #0000000d}.select-container.validation-success .select-trigger{border-color:#10b981!important}.select-container.validation-success .select-trigger:focus,.select-container.validation-success .select-trigger.focused,.select-container.validation-success .select-trigger.open{border-color:#10b981!important;box-shadow:0 0 0 3px #10b9811a,0 1px 2px #0000000d}.select-container.validation-info .select-trigger{border-color:#3b82f6!important}.select-container.validation-info .select-trigger:focus,.select-container.validation-info .select-trigger.focused,.select-container.validation-info .select-trigger.open{border-color:#3b82f6!important;box-shadow:0 0 0 3px #3b82f61a,0 1px 2px #0000000d}.validation-message{display:flex;align-items:center;gap:6px;margin-top:6px;font-size:13px;line-height:1.4;padding:4px 8px;border-radius:6px;transition:all .2s ease}.validation-message .validation-icon{display:flex;align-items:center;flex-shrink:0}.validation-message .validation-icon svg{display:block}.validation-message .validation-text{flex:1}.validation-message.validation-error{color:#dc2626;background:#fef2f2;border-left:3px solid #EF4444}.validation-message.validation-warning{color:#d97706;background:#fffbeb;border-left:3px solid #F59E0B}.validation-message.validation-success{color:#059669;background:#f0fdf4;border-left:3px solid #10B981}.validation-message.validation-info{color:#2563eb;background:#eff6ff;border-left:3px solid #3B82F6}.option.recent-option{position:relative;background:#fafbfc}.option.recent-option:after{content:\"\";position:absolute;left:0;top:0;bottom:0;width:3px;background:linear-gradient(to bottom,#2684ff,#0052cc);border-radius:0 3px 3px 0}.option.recent-option:hover:not(.disabled){background:#f4f5f7!important}.option.recent-option.selected{background:#e6f2ff!important}.option.recent-option.selected:hover:not(.disabled){background:#d4e8ff!important}.section-header{padding:8px 12px;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.5px;color:#6b7280;background:#f9fafb;border-bottom:1px solid #E5E7EB;position:sticky;top:0;z-index:2}.virtual-scroll-viewport{width:100%}.virtual-scroll-viewport ::ng-deep .cdk-virtual-scroll-content-wrapper{width:100%}.virtual-scroll-viewport ::ng-deep .cdk-virtual-scroll-spacer{display:none}.virtual-scroll-viewport .option{width:100%;box-sizing:border-box}@keyframes copyFlash{0%,to{background:transparent}50%{background:#2684ff1a}}.select-container.copying{animation:copyFlash .5s ease}.options-list.scrolling{position:relative}.options-list.scrolling:after{content:\"\";position:absolute;bottom:0;left:50%;transform:translate(-50%);width:20px;height:20px;border:2px solid #E5E7EB;border-top-color:#2684ff;border-radius:50%;animation:spin .8s linear infinite}.option[title]{cursor:help}.option.highlighted{position:relative}.option.highlighted:before{content:\"\";position:absolute;left:0;top:0;bottom:0;width:2px;background:#2684ff}@keyframes typeAheadPulse{0%,to{opacity:1}50%{opacity:.7}}.options-list.type-ahead-active .option.highlighted{animation:typeAheadPulse 1s ease-in-out}.select-container:focus-visible{outline:2px solid #2684FF;outline-offset:2px}@media (prefers-contrast: high){.select-trigger{border-width:2px}.option.highlighted{outline:2px solid currentColor;outline-offset:-2px}.validation-message{border-width:2px}}@media (prefers-reduced-motion: reduce){.select-trigger,.option,.tag,.dropdown,.validation-message{transition:none!important;animation:none!important}}\n"] }]
1161
- }], ctorParameters: () => [{ type: i1.DomSanitizer }], propDecorators: { options: [{
1508
+ }], template: "<div\n #selectContainer\n class=\"select-container {{selectSize}} {{containerSize}} theme-{{resolvedTheme()}} {{getValidationClass()}}\"\n [class.disabled]=\"isDisabled\"\n [class.rtl]=\"isRtl\"\n [class.has-validation]=\"validationState !== 'default'\"\n [class.compact]=\"compactMode\"\n [class.dark-mode]=\"isDarkMode()\"\n (clickOutside)=\"onClickOutside()\"\n role=\"combobox\"\n tabindex=\"0\"\n [attr.aria-controls]=\"'options-list'\"\n [attr.aria-expanded]=\"isOpen()\"\n [attr.aria-invalid]=\"validationState === 'error'\"\n (focus)=\"openMenuOnFocus && !isOpen() && toggleDropdown(); focus.emit()\"\n [style]=\"customStyles.container || ''\"\n>\n <!-- v2.3.0: Bulk Actions Bar (Above Position) -->\n @if (enableBulkActions && hasBulkActions() && bulkActionsPosition === 'above') {\n <div class=\"bulk-actions position-above\">\n <span class=\"bulk-label\">{{bulkActionsLabel}}</span>\n <div class=\"bulk-buttons\">\n @for (action of bulkActions; track action.id) {\n <button\n class=\"bulk-action-btn\"\n [disabled]=\"action.disabled || selectedOptions().length === 0\"\n (click)=\"executeBulkAction(action)\"\n [attr.aria-label]=\"action.label\"\n type=\"button\"\n >\n @if (action.icon) {\n <img [src]=\"action.icon\" alt=\"\" class=\"action-icon\" />\n }\n <span>{{action.label}}</span>\n </button>\n }\n </div>\n </div>\n }\n <!-- Main Select Trigger -->\n <div\n class=\"select-trigger\"\n [class.open]=\"isOpen()\"\n [class.focused]=\"isOpen()\"\n (click)=\"openMenuOnClick && toggleDropdown()\"\n [attr.tabindex]=\"isDisabled ? -1 : 0\"\n role=\"button\"\n aria-haspopup=\"listbox\"\n [attr.aria-expanded]=\"isOpen()\"\n [attr.aria-label]=\"placeholder\"\n >\n <!-- Selected Value Display -->\n <div class=\"select-value\">\n <!-- Multi-select Tags -->\n @if (isMulti && selectedOptions().length > 0) {\n <!-- v2.1.0: Drag-drop enabled tags -->\n @if (enableDragDrop) {\n <div class=\"tags\" cdkDropList cdkDropListOrientation=\"horizontal\" (cdkDropListDropped)=\"onTagsReorder($event)\" [class.dragging]=\"isDragging()\">\n @for (option of visibleTags(); track trackByValue($index, option)) {\n <span class=\"tag\" [class.disabled]=\"isDisabled\" cdkDrag (cdkDragStarted)=\"onDragStart()\" (cdkDragEnded)=\"onDragEnd()\" [@tag]>\n <div class=\"drag-handle\" cdkDragHandle>\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <path d=\"M9 3h2v2H9V3zm0 4h2v2H9V7zm0 4h2v2H9v-2zm0 4h2v2H9v-2zm0 4h2v2H9v-2zm4-16h2v2h-2V3zm0 4h2v2h-2V7zm0 4h2v2h-2v-2zm0 4h2v2h-2v-2zm0 4h2v2h-2v-2z\"/>\n </svg>\n </div>\n <span class=\"tag-label\">{{getOptionLabel(option)}}</span>\n @if (!isDisabled) {\n <button\n class=\"tag-remove\"\n (click)=\"removeOption(option, $event)\"\n [attr.aria-label]=\"'Remove ' + getOptionLabel(option)\"\n type=\"button\"\n >\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 20 20\">\n <path d=\"M14.348 14.849c-0.469 0.469-1.229 0.469-1.697 0l-2.651-3.030-2.651 3.029c-0.469 0.469-1.229 0.469-1.697 0-0.469-0.469-0.469-1.229 0-1.697l2.758-3.15-2.759-3.152c-0.469-0.469-0.469-1.228 0-1.697s1.228-0.469 1.697 0l2.652 3.031 2.651-3.031c0.469-0.469 1.228-0.469 1.697 0s0.469 1.229 0 1.697l-2.758 3.152 2.758 3.15c0.469 0.469 0.469 1.229 0 1.698z\"></path>\n </svg>\n </button>\n }\n <div *cdkDragPlaceholder class=\"drag-placeholder\">{{dragDropPlaceholder}}</div>\n </span>\n }\n </div>\n } @else {\n <div class=\"tags\">\n @for (option of visibleTags(); track trackByValue($index, option)) {\n <span class=\"tag\" [class.disabled]=\"isDisabled\" [@tag]>\n <span class=\"tag-label\">{{getOptionLabel(option)}}</span>\n @if (!isDisabled) {\n <button\n class=\"tag-remove\"\n (click)=\"removeOption(option, $event)\"\n [attr.aria-label]=\"'Remove ' + getOptionLabel(option)\"\n type=\"button\"\n >\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 20 20\">\n <path d=\"M14.348 14.849c-0.469 0.469-1.229 0.469-1.697 0l-2.651-3.030-2.651 3.029c-0.469 0.469-1.229 0.469-1.697 0-0.469-0.469-0.469-1.229 0-1.697l2.758-3.15-2.759-3.152c-0.469-0.469-0.469-1.228 0-1.697s1.228-0.469 1.697 0l2.652 3.031 2.651-3.031c0.469-0.469 1.228-0.469 1.697 0s0.469 1.229 0 1.697l-2.758 3.152 2.758 3.15c0.469 0.469 0.469 1.229 0 1.698z\"></path>\n </svg>\n </button>\n }\n </span>\n }\n </div>\n }\n\n <!-- v2.2.0: Tag overflow indicator -->\n @if (hiddenTagsCount() > 0) {\n @if (collapsibleTags) {\n <button\n class=\"tags-toggle\"\n (click)=\"toggleTagsExpanded()\"\n [attr.aria-label]=\"tagsExpanded() ? showLessTagsText : showAllTagsText\"\n type=\"button\"\n >\n {{ tagsExpanded() ? showLessTagsText : showAllTagsText }}\n </button>\n } @else {\n <span class=\"tags-overflow-indicator\">\n {{ getMoreTagsText() }}\n </span>\n }\n }\n } @else {\n <!-- Single Select or Placeholder -->\n <span class=\"placeholder\" [class.has-value]=\"selectedOptions().length > 0\">\n {{displayText()}}\n </span>\n }\n </div>\n\n <!-- Actions (Clear, Loading, Arrow) -->\n <div class=\"select-actions\">\n @if (isLoading || isLoadingAsync()) {\n <div class=\"spinner\"></div>\n }\n @if (isClearable && selectedOptions().length > 0 && !isDisabled && !isLoading && !isLoadingAsync()) {\n <button\n class=\"clear-button\"\n (click)=\"clearSelection($event)\"\n aria-label=\"Clear selection\"\n type=\"button\"\n >\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 20 20\">\n <path d=\"M14.348 14.849c-0.469 0.469-1.229 0.469-1.697 0l-2.651-3.030-2.651 3.029c-0.469 0.469-1.229 0.469-1.697 0-0.469-0.469-0.469-1.229 0-1.697l2.758-3.15-2.759-3.152c-0.469-0.469-0.469-1.228 0-1.697s1.228-0.469 1.697 0l2.652 3.031 2.651-3.031c0.469-0.469 1.228-0.469 1.697 0s0.469 1.229 0 1.697l-2.758 3.152 2.758 3.15c0.469 0.469 0.469 1.229 0 1.698z\"></path>\n </svg>\n </button>\n }\n <span class=\"separator\"></span>\n <span class=\"arrow\" [class.open]=\"isOpen()\">\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 20 20\">\n <path d=\"M4.516 7.548c0.436-0.446 1.043-0.481 1.576 0l3.908 3.747 3.908-3.747c0.533-0.481 1.141-0.446 1.574 0 0.436 0.445 0.408 1.197 0 1.615-0.406 0.418-4.695 4.502-4.695 4.502-0.217 0.223-0.502 0.335-0.787 0.335s-0.57-0.112-0.789-0.335c0 0-4.287-4.084-4.695-4.502s-0.436-1.17 0-1.615z\"></path>\n </svg>\n </span>\n </div>\n </div>\n\n <!-- Dropdown Menu -->\n @if (isOpen()) {\n <div\n #menuRef\n class=\"dropdown {{menuPlacement}}\"\n [class.fixed]=\"menuPosition === 'fixed'\"\n [style.max-height]=\"maxHeight\"\n role=\"listbox\"\n [attr.aria-multiselectable]=\"isMulti\"\n [@dropdown]\n >\n <!-- Search Input -->\n @if (isSearchable) {\n <div class=\"search-container\">\n <input\n #searchInput\n type=\"text\"\n class=\"search-input\"\n placeholder=\"Search...\"\n [value]=\"searchTerm()\"\n (input)=\"onSearchChange($any($event.target).value)\"\n (click)=\"$event.stopPropagation()\"\n aria-label=\"Search options\"\n aria-autocomplete=\"list\"\n />\n </div>\n }\n\n <!-- Options List -->\n <div\n class=\"options-list\"\n id=\"options-list\"\n (scroll)=\"onOptionsScroll($event)\"\n >\n <!-- Max Selection Message -->\n @if (isMaxSelectionReached()) {\n <div class=\"info-message max-selection\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 20 20\" fill=\"currentColor\">\n <path d=\"M10 0C4.477 0 0 4.477 0 10s4.477 10 10 10 10-4.477 10-10S15.523 0 10 0zm1 15H9v-2h2v2zm0-4H9V5h2v6z\"/>\n </svg>\n {{maxSelectedMessage}}\n </div>\n }\n\n <!-- Min Search Length Message -->\n @if (showMinSearchMessage()) {\n <div class=\"info-message min-search\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 20 20\" fill=\"currentColor\">\n <path d=\"M10 0C4.477 0 0 4.477 0 10s4.477 10 10 10 10-4.477 10-10S15.523 0 10 0zm1 15H9v-2h2v2zm0-4H9V5h2v6z\"/>\n </svg>\n {{minSearchMessage}} ({{searchTerm().length}}/{{minSearchLength}})\n </div>\n }\n\n @if (isLoadingAsync()) {\n <div class=\"loading-message\">{{loadingMessage()}}</div>\n } @else if (displayOptions().length === 0 && !showMinSearchMessage()) {\n <div class=\"no-options\">\n {{searchTerm() ? emptySearchText : emptyStateText}}\n </div>\n } @else if (!showMinSearchMessage()) {\n <!-- Select All (Multi-select only) -->\n @if (isMulti && showSelectAll && !searchTerm()) {\n <div class=\"select-all-container\">\n <button\n class=\"select-all-button\"\n (click)=\"allOptionsSelected() ? deselectAll() : selectAll()\"\n type=\"button\"\n >\n <input\n type=\"checkbox\"\n [checked]=\"allOptionsSelected()\"\n [indeterminate]=\"someOptionsSelected()\"\n tabindex=\"-1\"\n aria-hidden=\"true\"\n readonly\n />\n <span class=\"select-all-text\">\n {{allOptionsSelected() ? deselectAllText : selectAllText}}\n </span>\n <span class=\"select-all-count\">\n ({{selectedOptions().length}}/{{getEnabledOptionsCount()}})\n </span>\n </button>\n </div>\n }\n\n <!-- Grouped Options -->\n @if (isGrouped && groupedOptions()) {\n @for (group of groupedOptions() | keyvalue; track trackByGroup($index, [$any(group.key), $any(group.value)])) {\n <div class=\"option-group\">\n <div class=\"option-group-label\">{{group.key}}</div>\n @for (option of $any(group.value); track trackByValue($index, option); let idx = $index) {\n <div\n class=\"option\"\n [class.selected]=\"isSelected(option)\"\n [class.highlighted]=\"idx === highlightedIndex()\"\n [class.disabled]=\"isOptionDisabled(option) || (isMaxSelectionReached() && !isSelected(option))\"\n [class.hidden]=\"hideSelectedOptions && isSelected(option)\"\n [class.create-option]=\"option.__isCreate__\"\n (click)=\"selectOption(option)\"\n (keydown)=\"$event.key === 'Enter' && selectOption(option)\"\n role=\"option\"\n [attr.aria-selected]=\"isSelected(option)\"\n [attr.aria-disabled]=\"isOptionDisabled(option) || (isMaxSelectionReached() && !isSelected(option))\"\n tabindex=\"-1\"\n >\n <!-- Checkbox for multi-select -->\n @if (isMulti) {\n <input\n type=\"checkbox\"\n [checked]=\"isSelected(option)\"\n tabindex=\"-1\"\n aria-hidden=\"true\"\n readonly\n />\n }\n\n <!-- Option Icon -->\n @if (showOptionIcons && option.icon) {\n <div class=\"option-icon\">\n @if (option.icon.includes('<')) {\n <div [innerHTML]=\"sanitizeHtml(option.icon)\"></div>\n } @else {\n <img [src]=\"option.icon\" [alt]=\"getOptionLabel(option)\" />\n }\n </div>\n }\n\n <!-- Option Content -->\n <div class=\"option-content\">\n <div class=\"option-label\" [innerHTML]=\"highlightSearchTerm(getOptionLabel(option))\"></div>\n @if (option.description) {\n <div class=\"option-description\">{{option.description}}</div>\n }\n </div>\n\n <!-- Option Badge -->\n @if (showOptionBadges && option.badge) {\n <span\n class=\"option-badge\"\n [style.background-color]=\"option.badgeColor || currentTheme().primary\"\n >\n {{option.badge}}\n </span>\n }\n\n <!-- v2.1.0: Pin button -->\n @if (enablePinning) {\n <button\n class=\"pin-button\"\n [class.pinned]=\"isPinned(option)\"\n (click)=\"togglePin(option, $event)\"\n [attr.aria-label]=\"isPinned(option) ? 'Unpin option' : 'Pin option'\"\n type=\"button\"\n >\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n @if (isPinned(option)) {\n <path d=\"M16,12V4H17V2H7V4H8V12L6,14V16H11.2V22H12.8V16H18V14L16,12Z\"/>\n } @else {\n <path d=\"M16,12V4H17V2H7V4H8V12L6,14V16H11.2V22H12.8V16H18V14L16,12M8.8,14L10,12.8V4H14V12.8L15.2,14H8.8Z\"/>\n }\n </svg>\n </button>\n }\n\n <!-- Check Icon for selected -->\n @if (!isMulti && isSelected(option)) {\n <svg class=\"check-icon\" width=\"16\" height=\"16\" viewBox=\"0 0 20 20\">\n <path d=\"M7.629,14.566c0.125,0.125,0.291,0.188,0.456,0.188c0.164,0,0.329-0.062,0.456-0.188l8.219-8.221c0.252-0.252,0.252-0.659,0-0.911c-0.252-0.252-0.659-0.252-0.911,0l-7.764,7.763L4.152,9.267c-0.252-0.251-0.66-0.251-0.911,0c-0.252,0.252-0.252,0.66,0,0.911L7.629,14.566z\"></path>\n </svg>\n }\n </div>\n }\n </div>\n }\n } @else {\n <!-- v1.2.0: Recent Selections Header -->\n @if (showRecentSelections && !searchTerm() && recentSelections().length > 0) {\n <div class=\"section-header\">{{recentSelectionsLabel}}</div>\n }\n\n <!-- Regular (Ungrouped) Options - Virtual Scroll or Standard -->\n @if (enableVirtualScroll) {\n <!-- Virtual Scroll Mode -->\n <cdk-virtual-scroll-viewport\n [itemSize]=\"virtualScrollItemSize\"\n [minBufferPx]=\"virtualScrollMinBufferPx\"\n [maxBufferPx]=\"virtualScrollMaxBufferPx\"\n [style.height.px]=\"maxHeight\"\n class=\"virtual-scroll-viewport\"\n >\n @for (option of (showRecentSelections ? displayOptionsWithRecent() : displayOptions()); track trackByValue($index, option); let idx = $index) {\n <div\n class=\"option\"\n [class.selected]=\"isSelected(option)\"\n [class.highlighted]=\"idx === highlightedIndex()\"\n [class.disabled]=\"isOptionDisabled(option) || (isMaxSelectionReached() && !isSelected(option))\"\n [class.hidden]=\"hideSelectedOptions && isSelected(option)\"\n [class.create-option]=\"option.__isCreate__\"\n [class.recent-option]=\"option.__isRecent__\"\n (click)=\"selectOption(option)\"\n (keydown)=\"$event.key === 'Enter' && selectOption(option)\"\n (mouseenter)=\"showTooltip(option, idx)\"\n (mouseleave)=\"hideTooltip()\"\n role=\"option\"\n [attr.aria-selected]=\"isSelected(option)\"\n [attr.aria-disabled]=\"isOptionDisabled(option) || (isMaxSelectionReached() && !isSelected(option))\"\n [attr.title]=\"showTooltips ? getTooltipContent(option) : null\"\n tabindex=\"-1\"\n >\n <!-- Custom Template or Default Rendering -->\n @if (optionTemplate) {\n <ng-container\n *ngTemplateOutlet=\"optionTemplate; context: { $implicit: option, index: idx, selected: isSelected(option) }\"\n ></ng-container>\n } @else {\n <!-- Default Option Content (same as before) -->\n @if (isMulti) {\n <input\n type=\"checkbox\"\n [checked]=\"isSelected(option)\"\n tabindex=\"-1\"\n aria-hidden=\"true\"\n readonly\n />\n }\n\n @if (showOptionIcons && option.icon) {\n <div class=\"option-icon\">\n @if (option.icon.includes('<')) {\n <div [innerHTML]=\"sanitizeHtml(option.icon)\"></div>\n } @else {\n <img [src]=\"option.icon\" [alt]=\"getOptionLabel(option)\" />\n }\n </div>\n }\n\n <div class=\"option-content\">\n <div class=\"option-label\" [innerHTML]=\"highlightSearchTerm(getOptionLabel(option))\"></div>\n @if (option.description) {\n <div class=\"option-description\">{{option.description}}</div>\n }\n </div>\n\n @if (showOptionBadges && option.badge) {\n <span\n class=\"option-badge\"\n [style.background-color]=\"option.badgeColor || currentTheme().primary\"\n >\n {{option.badge}}\n </span>\n }\n\n <!-- v2.1.0: Pin button -->\n @if (enablePinning) {\n <button\n class=\"pin-button\"\n [class.pinned]=\"isPinned(option)\"\n (click)=\"togglePin(option, $event)\"\n [attr.aria-label]=\"isPinned(option) ? 'Unpin option' : 'Pin option'\"\n type=\"button\"\n >\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n @if (isPinned(option)) {\n <path d=\"M16,12V4H17V2H7V4H8V12L6,14V16H11.2V22H12.8V16H18V14L16,12Z\"/>\n } @else {\n <path d=\"M16,12V4H17V2H7V4H8V12L6,14V16H11.2V22H12.8V16H18V14L16,12M8.8,14L10,12.8V4H14V12.8L15.2,14H8.8Z\"/>\n }\n </svg>\n </button>\n }\n\n @if (!isMulti && isSelected(option)) {\n <svg class=\"check-icon\" width=\"16\" height=\"16\" viewBox=\"0 0 20 20\">\n <path d=\"M7.629,14.566c0.125,0.125,0.291,0.188,0.456,0.188c0.164,0,0.329-0.062,0.456-0.188l8.219-8.221c0.252-0.252,0.252-0.659,0-0.911c-0.252-0.252-0.659-0.252-0.911,0l-7.764,7.763L4.152,9.267c-0.252-0.251-0.66-0.251-0.911,0c-0.252,0.252-0.252,0.66,0,0.911L7.629,14.566z\"></path>\n </svg>\n }\n }\n </div>\n }\n </cdk-virtual-scroll-viewport>\n } @else {\n <!-- Standard Rendering (Non-Virtual Scroll) -->\n @for (option of (showRecentSelections ? displayOptionsWithRecent() : displayOptions()); track trackByValue($index, option); let idx = $index) {\n <div\n class=\"option\"\n [class.selected]=\"isSelected(option)\"\n [class.highlighted]=\"idx === highlightedIndex()\"\n [class.disabled]=\"isOptionDisabled(option) || (isMaxSelectionReached() && !isSelected(option))\"\n [class.hidden]=\"hideSelectedOptions && isSelected(option)\"\n [class.create-option]=\"option.__isCreate__\"\n [class.recent-option]=\"option.__isRecent__\"\n (click)=\"selectOption(option)\"\n (keydown)=\"$event.key === 'Enter' && selectOption(option)\"\n (mouseenter)=\"showTooltip(option, idx)\"\n (mouseleave)=\"hideTooltip()\"\n role=\"option\"\n [attr.aria-selected]=\"isSelected(option)\"\n [attr.aria-disabled]=\"isOptionDisabled(option) || (isMaxSelectionReached() && !isSelected(option))\"\n [attr.title]=\"showTooltips ? getTooltipContent(option) : null\"\n tabindex=\"-1\"\n >\n <!-- Custom Template or Default Rendering -->\n @if (optionTemplate) {\n <ng-container\n *ngTemplateOutlet=\"optionTemplate; context: { $implicit: option, index: idx, selected: isSelected(option) }\"\n ></ng-container>\n } @else {\n <!-- Default Option Content -->\n @if (isMulti) {\n <input\n type=\"checkbox\"\n [checked]=\"isSelected(option)\"\n tabindex=\"-1\"\n aria-hidden=\"true\"\n readonly\n />\n }\n\n @if (showOptionIcons && option.icon) {\n <div class=\"option-icon\">\n @if (option.icon.includes('<')) {\n <div [innerHTML]=\"sanitizeHtml(option.icon)\"></div>\n } @else {\n <img [src]=\"option.icon\" [alt]=\"getOptionLabel(option)\" />\n }\n </div>\n }\n\n <div class=\"option-content\">\n <div class=\"option-label\" [innerHTML]=\"highlightSearchTerm(getOptionLabel(option))\"></div>\n @if (option.description) {\n <div class=\"option-description\">{{option.description}}</div>\n }\n </div>\n\n @if (showOptionBadges && option.badge) {\n <span\n class=\"option-badge\"\n [style.background-color]=\"option.badgeColor || currentTheme().primary\"\n >\n {{option.badge}}\n </span>\n }\n\n <!-- v2.1.0: Pin button -->\n @if (enablePinning) {\n <button\n class=\"pin-button\"\n [class.pinned]=\"isPinned(option)\"\n (click)=\"togglePin(option, $event)\"\n [attr.aria-label]=\"isPinned(option) ? 'Unpin option' : 'Pin option'\"\n type=\"button\"\n >\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n @if (isPinned(option)) {\n <path d=\"M16,12V4H17V2H7V4H8V12L6,14V16H11.2V22H12.8V16H18V14L16,12Z\"/>\n } @else {\n <path d=\"M16,12V4H17V2H7V4H8V12L6,14V16H11.2V22H12.8V16H18V14L16,12M8.8,14L10,12.8V4H14V12.8L15.2,14H8.8Z\"/>\n }\n </svg>\n </button>\n }\n\n @if (!isMulti && isSelected(option)) {\n <svg class=\"check-icon\" width=\"16\" height=\"16\" viewBox=\"0 0 20 20\">\n <path d=\"M7.629,14.566c0.125,0.125,0.291,0.188,0.456,0.188c0.164,0,0.329-0.062,0.456-0.188l8.219-8.221c0.252-0.252,0.252-0.659,0-0.911c-0.252-0.252-0.659-0.252-0.911,0l-7.764,7.763L4.152,9.267c-0.252-0.251-0.66-0.251-0.911,0c-0.252,0.252-0.252,0.66,0,0.911L7.629,14.566z\"></path>\n </svg>\n }\n }\n </div>\n }\n }\n }\n }\n </div>\n </div>\n }\n\n <!-- v1.2.0: Validation Message -->\n @if (validationMessage && validationState !== 'default') {\n <div class=\"validation-message {{getValidationClass()}}\">\n @if (showValidationIcon) {\n <span class=\"validation-icon\">\n @if (validationState === 'error') {\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 20 20\" fill=\"currentColor\">\n <path d=\"M10 0C4.477 0 0 4.477 0 10s4.477 10 10 10 10-4.477 10-10S15.523 0 10 0zm1 15H9v-2h2v2zm0-4H9V5h2v6z\"/>\n </svg>\n } @else if (validationState === 'warning') {\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 20 20\" fill=\"currentColor\">\n <path d=\"M10 0C4.477 0 0 4.477 0 10s4.477 10 10 10 10-4.477 10-10S15.523 0 10 0zm1 15H9v-2h2v2zm0-4H9V5h2v6z\"/>\n </svg>\n } @else if (validationState === 'success') {\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 20 20\" fill=\"currentColor\">\n <path d=\"M10 0C4.477 0 0 4.477 0 10s4.477 10 10 10 10-4.477 10-10S15.523 0 10 0zm-1.707 13.707l-3.5-3.5 1.414-1.414L9 11.586l5.293-5.293 1.414 1.414-6.5 6.5z\"/>\n </svg>\n } @else if (validationState === 'info') {\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 20 20\" fill=\"currentColor\">\n <path d=\"M10 0C4.477 0 0 4.477 0 10s4.477 10 10 10 10-4.477 10-10S15.523 0 10 0zm1 15H9v-6h2v6zm0-8H9V5h2v2z\"/>\n </svg>\n }\n </span>\n }\n <span class=\"validation-text\">{{validationMessage}}</span>\n </div>\n }\n</div>\n\n<!-- Hidden native select for form compatibility -->\n@if (isMulti) {\n <select [name]=\"name\" [id]=\"id\" multiple style=\"display: none;\" [attr.aria-hidden]=\"true\">\n @for (option of selectedOptions(); track trackByValue($index, option)) {\n <option [value]=\"getOptionValue(option)\" selected>{{getOptionLabel(option)}}</option>\n }\n </select>\n} @else {\n @if (selectedOptions().length > 0) {\n <select [name]=\"name\" [id]=\"id\" style=\"display: none;\" [attr.aria-hidden]=\"true\">\n <option [value]=\"getOptionValue(selectedOptions()[0])\" selected>{{getOptionLabel(selectedOptions()[0])}}</option>\n </select>\n }\n}\n", styles: [":host{display:block;width:100%}.select-container{position:relative;width:100%;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif}.smaller{font-size:11px}.small{font-size:13px}.medium{font-size:14px}.large{font-size:16px}.larger{font-size:18px}.xs .select-trigger{min-height:28px;padding:4px 8px}.sm .select-trigger{min-height:32px;padding:6px 10px}.md .select-trigger{min-height:40px;padding:8px 12px}.lg .select-trigger{min-height:48px;padding:10px 14px}.xl .select-trigger{min-height:56px;padding:12px 16px}.select-container.disabled{opacity:.6;cursor:not-allowed}.select-container.rtl{direction:rtl}.select-trigger{display:flex;align-items:center;justify-content:space-between;padding:8px 12px;background:#fff;border:1.5px solid #D1D5DB;border-radius:10px;cursor:pointer;transition:all .2s cubic-bezier(.4,0,.2,1);min-height:38px;gap:8px;box-shadow:0 1px 2px #0000000d}.select-trigger:hover:not(.disabled){border-color:#9ca3af;box-shadow:0 2px 4px #00000014}.select-trigger:focus,.select-trigger.focused{outline:none;border-color:#2684ff;box-shadow:0 0 0 3px #2684ff1a,0 1px 2px #0000000d}.select-trigger.open{border-color:#2684ff;box-shadow:0 0 0 3px #2684ff1a,0 1px 2px #0000000d}.select-value{flex:1;display:flex;align-items:center;min-width:0;gap:4px}.placeholder{color:#999;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.placeholder.has-value{color:#333}.tags{display:flex;flex-wrap:wrap;gap:4px;flex:1}.tag{display:inline-flex;align-items:center;gap:4px;padding:4px 8px;background:#e6f2ff;color:#0052cc;border-radius:6px;font-size:.875em;font-weight:500;white-space:nowrap;border:1px solid #CCE0FF;box-shadow:0 1px 2px #0000000d}.tag.disabled{background:#f0f0f0;color:#666;border-color:#d9d9d9}.tag-label{line-height:1.2}.tag-remove{background:none;border:none;color:inherit;cursor:pointer;padding:0;display:flex;align-items:center;justify-content:center;border-radius:2px;transition:all .15s}.tag-remove:hover{background:#0052cc26}.tag-remove svg{fill:currentColor}.tags.dragging .tag{cursor:move}.drag-handle{display:flex;align-items:center;justify-content:center;cursor:move;padding:0;margin-left:-4px;opacity:.5;transition:opacity .15s}.drag-handle svg{fill:currentColor}.drag-handle:hover{opacity:.8}.cdk-drag-preview{box-shadow:0 5px 15px #0003;opacity:.8}.cdk-drag-animating{transition:transform .25s cubic-bezier(0,0,.2,1)}.drag-placeholder{background:#0052cc1a;border:2px dashed var(--ps-primary, #0052cc);border-radius:6px;padding:4px 8px;min-height:28px;display:flex;align-items:center;justify-content:center;color:var(--ps-primary, #0052cc);font-size:.75em;opacity:.6}.cdk-drop-list-dragging .cdk-drag{transition:transform .25s cubic-bezier(0,0,.2,1)}.tags-toggle{background:none;border:none;color:var(--ps-primary, #0052cc);cursor:pointer;font-size:.875em;font-weight:500;padding:4px 8px;border-radius:4px;transition:all .15s;white-space:nowrap}.tags-toggle:hover{background:#0052cc1a;text-decoration:underline}.tags-toggle:focus{outline:2px solid var(--ps-primary, #0052cc);outline-offset:2px}.tags-overflow-indicator{display:inline-flex;align-items:center;padding:4px 8px;background:#f3f4f6;color:#6b7280;border-radius:6px;font-size:.875em;font-weight:500;white-space:nowrap;border:1px solid #d1d5db}.option-label mark{background-color:#ffeb3b;color:#000;padding:0 2px;border-radius:2px;font-weight:600}.select-actions{display:flex;align-items:center;gap:4px;flex-shrink:0}.clear-button{background:none;border:none;color:#999;cursor:pointer;padding:4px;display:flex;align-items:center;justify-content:center;border-radius:3px;transition:all .15s}.clear-button:hover{color:#333;background:#f0f0f0}.clear-button svg{fill:currentColor}.separator{width:1px;height:24px;background:#ccc;align-self:stretch}.arrow{color:#999;transition:transform .2s cubic-bezier(.4,0,.2,1);display:flex;align-items:center;padding:4px}.arrow svg{fill:currentColor}.arrow.open{transform:rotate(180deg)}.spinner{width:16px;height:16px;border:2px solid #f3f3f3;border-top:2px solid #2684FF;border-radius:50%;animation:spin .6s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}.dropdown{position:absolute;top:calc(100% + 8px);left:0;right:0;background:#fffffffa;border:1px solid #E5E7EB;border-radius:12px;box-shadow:0 20px 25px -5px #0000001a,0 10px 10px -5px #0000000a,0 0 0 1px #0000000d;z-index:1000;overflow:hidden;backdrop-filter:blur(12px) saturate(180%);-webkit-backdrop-filter:blur(12px) saturate(180%)}.dropdown.fixed{position:fixed}.dropdown.top{top:auto;bottom:calc(100% + 4px)}.search-container{padding:8px;border-bottom:1px solid #f0f0f0;background:#fff}.search-input{width:100%;padding:6px 8px;border:1px solid #cccccc;border-radius:3px;font-size:inherit;outline:none;transition:border-color .15s}.search-input:focus{border-color:#2684ff;box-shadow:0 0 0 1px #2684ff}.options-list{max-height:inherit;overflow-y:auto;overflow-x:hidden;padding:4px 0}.options-list::-webkit-scrollbar{width:8px}.options-list::-webkit-scrollbar-track{background:transparent}.options-list::-webkit-scrollbar-thumb{background:#d9d9d9;border-radius:4px}.options-list::-webkit-scrollbar-thumb:hover{background:#b3b3b3}.select-all-container{padding:8px;border-bottom:1px solid #E5E7EB;background:#f9fafb}.select-all-button{width:100%;display:flex;align-items:center;gap:8px;padding:6px 8px;background:#fff;border:1px solid #D1D5DB;border-radius:6px;cursor:pointer;transition:all .15s;font-size:inherit;color:inherit;font-family:inherit}.select-all-button:hover{background:#f3f4f6;border-color:#9ca3af}.select-all-button input[type=checkbox]{cursor:pointer;width:16px;height:16px;margin:0;accent-color:#2684FF}.select-all-text{flex:1;text-align:left;font-weight:500}.select-all-count{color:#6b7280;font-size:.9em}.option-group{margin:4px 0}.option-group-label{padding:8px 12px 4px;font-size:.75em;font-weight:600;color:#6b7280;text-transform:uppercase;letter-spacing:.05em;background:#f9fafb;border-bottom:1px solid #E5E7EB;position:sticky;top:0;z-index:1}.option-group .option{padding-left:20px}.option{display:flex;align-items:center;gap:10px;padding:10px 12px;cursor:pointer;transition:all .15s cubic-bezier(.4,0,.2,1);position:relative;min-height:40px}.option-content{flex:1;display:flex;flex-direction:column;gap:2px;min-width:0}.option-icon{display:flex;align-items:center;justify-content:center;width:32px;height:32px;flex-shrink:0;border-radius:6px;overflow:hidden;background:#f3f4f6}.option-icon img{width:100%;height:100%;object-fit:cover}.option-badge{padding:2px 8px;border-radius:12px;font-size:.75em;font-weight:500;color:#374151;background:#e5e7eb;flex-shrink:0;white-space:nowrap}.option:hover:not(.disabled):not(.create-option){background:#deebff}.option.highlighted{background:#deebff}.option.selected:not(.create-option){background:#e6f2ff;font-weight:500}.option.disabled{opacity:.5;cursor:not-allowed;background:transparent!important}.option.hidden{display:none}.option.create-option{color:#2684ff;font-weight:500;background:#f0f6ff}.option.create-option:hover{background:#e6f2ff}.option input[type=checkbox]{cursor:pointer;width:16px;height:16px;margin:0;accent-color:#2684FF}.option-label{font-weight:500;color:#1f2937;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;line-height:1.4}.option-description{font-size:.875em;color:#6b7280;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;line-height:1.3}.check-icon{color:#2684ff;display:flex;align-items:center;margin-left:auto}.check-icon svg{fill:currentColor}.pin-button{background:none;border:none;color:#999;cursor:pointer;padding:4px;display:flex;align-items:center;justify-content:center;border-radius:3px;transition:all .15s;margin-left:auto;opacity:.5}.pin-button svg{fill:currentColor}.pin-button:hover{opacity:1;background:#0000000d}.pin-button.pinned{color:var(--ps-primary, #0052cc);opacity:1}.option.pinned-option{background:#0052cc0d;border-left:3px solid var(--ps-primary, #0052cc);padding-left:9px}.no-options,.loading-message{padding:16px 12px;text-align:center;color:#999;font-size:.95em}.info-message{padding:12px;margin:8px;border-radius:6px;font-size:.9em;display:flex;align-items:center;gap:8px}.info-message.max-selection{background:#fff4e5;color:#f57c00;border:1px solid #FFE0B2}.info-message.min-search{background:#e3f2fd;color:#1976d2;border:1px solid #BBDEFB}.info-message svg{flex-shrink:0}.rtl .select-actions,.rtl .tags,.rtl .option{flex-direction:row-reverse}.theme-blue .select-trigger:focus,.theme-blue .select-trigger.focused,.theme-blue .select-trigger.open{border-color:#2684ff;box-shadow:0 0 0 1px #2684ff}.theme-blue .option:hover:not(.disabled):not(.create-option),.theme-blue .option.highlighted{background:#deebff}.theme-blue .option.selected:not(.create-option){background:#e6f2ff}.theme-blue .tag{background:#e6f2ff;color:#0052cc;border-color:#cce0ff}.theme-blue .check-icon,.theme-blue .option.create-option{color:#2684ff}.theme-blue .spinner{border-top-color:#2684ff}.theme-blue .search-input:focus{border-color:#2684ff;box-shadow:0 0 0 1px #2684ff}.theme-purple .select-trigger:focus,.theme-purple .select-trigger.focused,.theme-purple .select-trigger.open{border-color:#9333ea;box-shadow:0 0 0 1px #9333ea}.theme-purple .option:hover:not(.disabled):not(.create-option),.theme-purple .option.highlighted{background:#f3e8ff}.theme-purple .option.selected:not(.create-option){background:#faf5ff}.theme-purple .tag{background:#faf5ff;color:#7e22ce;border-color:#e9d5ff}.theme-purple .check-icon,.theme-purple .option.create-option{color:#9333ea}.theme-purple .spinner{border-top-color:#9333ea}.theme-purple .search-input:focus{border-color:#9333ea;box-shadow:0 0 0 1px #9333ea}.theme-green .select-trigger:focus,.theme-green .select-trigger.focused,.theme-green .select-trigger.open{border-color:#10b981;box-shadow:0 0 0 1px #10b981}.theme-green .option:hover:not(.disabled):not(.create-option),.theme-green .option.highlighted{background:#d1fae5}.theme-green .option.selected:not(.create-option){background:#ecfdf5}.theme-green .tag{background:#ecfdf5;color:#059669;border-color:#a7f3d0}.theme-green .check-icon,.theme-green .option.create-option{color:#10b981}.theme-green .spinner{border-top-color:#10b981}.theme-green .search-input:focus{border-color:#10b981;box-shadow:0 0 0 1px #10b981}.theme-red .select-trigger:focus,.theme-red .select-trigger.focused,.theme-red .select-trigger.open{border-color:#ef4444;box-shadow:0 0 0 1px #ef4444}.theme-red .option:hover:not(.disabled):not(.create-option),.theme-red .option.highlighted{background:#fee2e2}.theme-red .option.selected:not(.create-option){background:#fef2f2}.theme-red .tag{background:#fef2f2;color:#dc2626;border-color:#fecaca}.theme-red .check-icon,.theme-red .option.create-option{color:#ef4444}.theme-red .spinner{border-top-color:#ef4444}.theme-red .search-input:focus{border-color:#ef4444;box-shadow:0 0 0 1px #ef4444}.theme-orange .select-trigger:focus,.theme-orange .select-trigger.focused,.theme-orange .select-trigger.open{border-color:#f97316;box-shadow:0 0 0 1px #f97316}.theme-orange .option:hover:not(.disabled):not(.create-option),.theme-orange .option.highlighted{background:#ffedd5}.theme-orange .option.selected:not(.create-option){background:#fff7ed}.theme-orange .tag{background:#fff7ed;color:#ea580c;border-color:#fed7aa}.theme-orange .check-icon,.theme-orange .option.create-option{color:#f97316}.theme-orange .spinner{border-top-color:#f97316}.theme-orange .search-input:focus{border-color:#f97316;box-shadow:0 0 0 1px #f97316}.theme-pink .select-trigger:focus,.theme-pink .select-trigger.focused,.theme-pink .select-trigger.open{border-color:#ec4899;box-shadow:0 0 0 1px #ec4899}.theme-pink .option:hover:not(.disabled):not(.create-option),.theme-pink .option.highlighted{background:#fce7f3}.theme-pink .option.selected:not(.create-option){background:#fdf2f8}.theme-pink .tag{background:#fdf2f8;color:#db2777;border-color:#fbcfe8}.theme-pink .check-icon,.theme-pink .option.create-option{color:#ec4899}.theme-pink .spinner{border-top-color:#ec4899}.theme-pink .search-input:focus{border-color:#ec4899;box-shadow:0 0 0 1px #ec4899}.theme-dark .select-trigger:focus,.theme-dark .select-trigger.focused,.theme-dark .select-trigger.open{border-color:#1f2937;box-shadow:0 0 0 1px #1f2937}.theme-dark .option:hover:not(.disabled):not(.create-option),.theme-dark .option.highlighted{background:#e5e7eb}.theme-dark .option.selected:not(.create-option){background:#f3f4f6}.theme-dark .tag{background:#f3f4f6;color:#111827;border-color:#d1d5db}.theme-dark .check-icon,.theme-dark .option.create-option{color:#1f2937}.theme-dark .spinner{border-top-color:#1f2937}.theme-dark .search-input:focus{border-color:#1f2937;box-shadow:0 0 0 1px #1f2937}.select-container.has-validation .select-trigger{transition:all .2s cubic-bezier(.4,0,.2,1)}.select-container.validation-error .select-trigger{border-color:#ef4444!important}.select-container.validation-error .select-trigger:focus,.select-container.validation-error .select-trigger.focused,.select-container.validation-error .select-trigger.open{border-color:#ef4444!important;box-shadow:0 0 0 3px #ef44441a,0 1px 2px #0000000d}.select-container.validation-warning .select-trigger{border-color:#f59e0b!important}.select-container.validation-warning .select-trigger:focus,.select-container.validation-warning .select-trigger.focused,.select-container.validation-warning .select-trigger.open{border-color:#f59e0b!important;box-shadow:0 0 0 3px #f59e0b1a,0 1px 2px #0000000d}.select-container.validation-success .select-trigger{border-color:#10b981!important}.select-container.validation-success .select-trigger:focus,.select-container.validation-success .select-trigger.focused,.select-container.validation-success .select-trigger.open{border-color:#10b981!important;box-shadow:0 0 0 3px #10b9811a,0 1px 2px #0000000d}.select-container.validation-info .select-trigger{border-color:#3b82f6!important}.select-container.validation-info .select-trigger:focus,.select-container.validation-info .select-trigger.focused,.select-container.validation-info .select-trigger.open{border-color:#3b82f6!important;box-shadow:0 0 0 3px #3b82f61a,0 1px 2px #0000000d}.validation-message{display:flex;align-items:center;gap:6px;margin-top:6px;font-size:13px;line-height:1.4;padding:4px 8px;border-radius:6px;transition:all .2s ease}.validation-message .validation-icon{display:flex;align-items:center;flex-shrink:0}.validation-message .validation-icon svg{display:block}.validation-message .validation-text{flex:1}.validation-message.validation-error{color:#dc2626;background:#fef2f2;border-left:3px solid #EF4444}.validation-message.validation-warning{color:#d97706;background:#fffbeb;border-left:3px solid #F59E0B}.validation-message.validation-success{color:#059669;background:#f0fdf4;border-left:3px solid #10B981}.validation-message.validation-info{color:#2563eb;background:#eff6ff;border-left:3px solid #3B82F6}.option.recent-option{position:relative;background:#fafbfc}.option.recent-option:after{content:\"\";position:absolute;left:0;top:0;bottom:0;width:3px;background:linear-gradient(to bottom,#2684ff,#0052cc);border-radius:0 3px 3px 0}.option.recent-option:hover:not(.disabled){background:#f4f5f7!important}.option.recent-option.selected{background:#e6f2ff!important}.option.recent-option.selected:hover:not(.disabled){background:#d4e8ff!important}.section-header{padding:8px 12px;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.5px;color:#6b7280;background:#f9fafb;border-bottom:1px solid #E5E7EB;position:sticky;top:0;z-index:2}.virtual-scroll-viewport{width:100%}.virtual-scroll-viewport ::ng-deep .cdk-virtual-scroll-content-wrapper{width:100%}.virtual-scroll-viewport ::ng-deep .cdk-virtual-scroll-spacer{display:none}.virtual-scroll-viewport .option{width:100%;box-sizing:border-box}@keyframes copyFlash{0%,to{background:transparent}50%{background:#2684ff1a}}.select-container.copying{animation:copyFlash .5s ease}.options-list.scrolling{position:relative}.options-list.scrolling:after{content:\"\";position:absolute;bottom:0;left:50%;transform:translate(-50%);width:20px;height:20px;border:2px solid #E5E7EB;border-top-color:#2684ff;border-radius:50%;animation:spin .8s linear infinite}.option[title]{cursor:help}.option.highlighted{position:relative}.option.highlighted:before{content:\"\";position:absolute;left:0;top:0;bottom:0;width:2px;background:#2684ff}@keyframes typeAheadPulse{0%,to{opacity:1}50%{opacity:.7}}.options-list.type-ahead-active .option.highlighted{animation:typeAheadPulse 1s ease-in-out}.select-container:focus-visible{outline:2px solid #2684FF;outline-offset:2px}@media (prefers-contrast: high){.select-trigger{border-width:2px}.option.highlighted{outline:2px solid currentColor;outline-offset:-2px}.validation-message{border-width:2px}}@media (prefers-reduced-motion: reduce){.select-trigger,.option,.tag,.dropdown,.validation-message{transition:none!important;animation:none!important}}.select-container.dark-mode{--ps-bg: #1f2937;--ps-bg-hover: #374151;--ps-border: #4b5563;--ps-text: #f3f4f6;--ps-text-secondary: #d1d5db;--ps-placeholder: #9ca3af;--ps-disabled-bg: #111827;--ps-disabled-text: #6b7280;--ps-shadow: 0 4px 6px rgba(0, 0, 0, .3)}.select-container.dark-mode .select-trigger{background:var(--ps-bg);border-color:var(--ps-border);color:var(--ps-text)}.select-container.dark-mode .select-trigger:hover:not(.disabled){background:var(--ps-bg-hover);border-color:#6b7280}.select-container.dark-mode .menu{background:var(--ps-bg);border-color:var(--ps-border);box-shadow:var(--ps-shadow)}.select-container.dark-mode .option{color:var(--ps-text)}.select-container.dark-mode .option:hover:not(.disabled){background:var(--ps-bg-hover)}.select-container.dark-mode .option.highlighted{background:var(--ps-bg-hover)}.select-container.dark-mode .option.selected{background:#3b82f633}.select-container.dark-mode .tag{background:var(--ps-bg-hover);color:var(--ps-text);border-color:var(--ps-border)}.select-container.dark-mode .empty-state,.select-container.dark-mode .min-search-message{color:var(--ps-text-secondary)}.select-container.dark-mode .group-label{background:var(--ps-bg);color:var(--ps-text-secondary);border-bottom-color:var(--ps-border)}.skeleton-loader{padding:8px 0}.skeleton-loader .skeleton-item{padding:8px 12px;display:flex;flex-direction:column;gap:6px}.skeleton-loader .skeleton-line{background:linear-gradient(90deg,#e5e7eb,#f3f4f6,#e5e7eb);background-size:200% 100%;animation:skeleton-loading 1.5s infinite;border-radius:4px;height:12px}.skeleton-loader .skeleton-line.skeleton-title{width:80%;height:14px}.skeleton-loader .skeleton-line.skeleton-subtitle{width:60%;height:10px}@keyframes skeleton-loading{0%{background-position:200% 0}to{background-position:-200% 0}}.select-container.dark-mode .skeleton-line{background:linear-gradient(90deg,#374151,#4b5563,#374151);background-size:200% 100%}.select-container.compact .select-trigger{min-height:32px;padding:4px 8px;gap:4px}.select-container.compact .tag{padding:2px 6px;font-size:.75rem;gap:4px;height:24px}.select-container.compact .tag .tag-label{font-size:.75rem}.select-container.compact .tag-remove{width:12px;height:12px}.select-container.compact .tag-remove svg{width:10px;height:10px}.select-container.compact .option{min-height:32px;padding:6px 10px;font-size:.875rem}.select-container.compact .menu{max-height:250px}.select-container.compact .select-all-container{padding:6px 10px}.select-container.compact .group-label{padding:6px 10px;font-size:.75rem}.select-container.compact .empty-state,.select-container.compact .min-search-message{padding:12px;font-size:.875rem}.select-container.compact .search-input input{font-size:.875rem;padding:4px 0}.select-container.compact .indicators{gap:4px}.select-container.compact .indicators .indicator{width:14px;height:14px}.select-container.compact .indicators .indicator svg{width:12px;height:12px}.option-checkbox{display:flex;align-items:center;justify-content:center;width:20px;height:20px;flex-shrink:0;margin-right:8px;transition:all .2s ease}.option-checkbox.checkbox-default{border:2px solid #d1d5db;border-radius:4px;background:#fff}.option-checkbox.checkbox-default svg{color:#10b981}.option-checkbox.checkbox-filled{background:var(--ps-primary);border-radius:4px;border:none}.option-checkbox.checkbox-filled svg{color:#fff}.option-checkbox.checkbox-outlined{border:2px solid var(--ps-primary);border-radius:4px;background:transparent}.option-checkbox.checkbox-outlined svg{color:var(--ps-primary)}.option-checkbox .checkbox-empty{width:12px;height:12px;border-radius:2px;background:transparent}.option.with-checkbox{display:flex;align-items:center;gap:10px}.option.with-checkbox.checkbox-right{flex-direction:row-reverse}.option.with-checkbox.checkbox-right .option-checkbox{margin-right:0;margin-left:8px}.select-container.dark-mode .option-checkbox.checkbox-default{border-color:#6b7280;background:#374151}.bulk-actions{padding:12px;background:#f9fafb;border-bottom:1px solid #e5e7eb;display:flex;align-items:center;gap:12px;border-radius:8px 8px 0 0;flex-wrap:wrap}.bulk-actions.position-above{border-bottom:1px solid #e5e7eb;border-radius:8px 8px 0 0;order:-1}.bulk-actions.position-below{border-top:1px solid #e5e7eb;border-bottom:none;border-radius:0 0 8px 8px;order:99}.bulk-actions.position-float{position:sticky;bottom:0;background:#fffffff2;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);z-index:10;border-top:1px solid #e5e7eb;border-bottom:none}.bulk-actions .bulk-label{font-weight:600;color:#4b5563;font-size:.875rem;white-space:nowrap}.bulk-actions .bulk-buttons{display:flex;gap:8px;flex-wrap:wrap}.bulk-actions .bulk-action-btn{padding:6px 12px;background:#fff;border:1px solid #d1d5db;border-radius:6px;cursor:pointer;display:flex;align-items:center;gap:6px;font-size:.875rem;font-weight:500;color:#374151;transition:all .2s ease;white-space:nowrap}.bulk-actions .bulk-action-btn:hover:not(:disabled){background:#f3f4f6;border-color:#9ca3af;transform:translateY(-1px);box-shadow:0 2px 4px #0000001a}.bulk-actions .bulk-action-btn:active:not(:disabled){transform:translateY(0);box-shadow:none}.bulk-actions .bulk-action-btn:disabled{opacity:.5;cursor:not-allowed}.bulk-actions .bulk-action-btn .action-icon{width:16px;height:16px;object-fit:contain}.select-container.dark-mode .bulk-actions{background:#1f2937;border-color:#4b5563}.select-container.dark-mode .bulk-actions.position-float{background:#1f2937f2}.select-container.dark-mode .bulk-actions .bulk-label{color:#d1d5db}.select-container.dark-mode .bulk-actions .bulk-action-btn{background:#374151;border-color:#4b5563;color:#f3f4f6}.select-container.dark-mode .bulk-actions .bulk-action-btn:hover:not(:disabled){background:#4b5563;border-color:#6b7280}.select-container.compact .bulk-actions{padding:8px;gap:8px}.select-container.compact .bulk-actions .bulk-label{font-size:.75rem}.select-container.compact .bulk-actions .bulk-action-btn{padding:4px 8px;font-size:.75rem}.select-container.compact .bulk-actions .bulk-action-btn .action-icon{width:14px;height:14px}.select-container.compact.dark-mode .skeleton-line{height:10px}.select-container.compact.dark-mode .skeleton-line.skeleton-title{height:12px}.select-container.compact.dark-mode .bulk-actions{padding:6px 8px}.select-container.compact.dark-mode .option-checkbox{width:16px;height:16px}\n"] }]
1509
+ }], ctorParameters: () => [{ type: i1.DomSanitizer }, { type: DarkModeProvider }], propDecorators: { options: [{
1162
1510
  type: Input
1163
1511
  }], placeholder: [{
1164
1512
  type: Input
@@ -1332,6 +1680,50 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImpo
1332
1680
  type: Input
1333
1681
  }], showLessTagsText: [{
1334
1682
  type: Input
1683
+ }], enableFuzzySearch: [{
1684
+ type: Input
1685
+ }], fuzzySearchThreshold: [{
1686
+ type: Input
1687
+ }], fuzzySearchCaseSensitive: [{
1688
+ type: Input
1689
+ }], enableAutoThemeDetection: [{
1690
+ type: Input
1691
+ }], colorScheme: [{
1692
+ type: Input
1693
+ }], darkModeTheme: [{
1694
+ type: Input
1695
+ }], lightModeTheme: [{
1696
+ type: Input
1697
+ }], enableLoadingSkeleton: [{
1698
+ type: Input
1699
+ }], skeletonItemCount: [{
1700
+ type: Input
1701
+ }], skeletonItemHeight: [{
1702
+ type: Input
1703
+ }], skeletonAnimationDelay: [{
1704
+ type: Input
1705
+ }], compactMode: [{
1706
+ type: Input
1707
+ }], showOptionCheckboxes: [{
1708
+ type: Input
1709
+ }], checkboxPosition: [{
1710
+ type: Input
1711
+ }], checkboxStyle: [{
1712
+ type: Input
1713
+ }], bulkActions: [{
1714
+ type: Input
1715
+ }], enableBulkActions: [{
1716
+ type: Input
1717
+ }], bulkActionsPosition: [{
1718
+ type: Input
1719
+ }], bulkActionsLabel: [{
1720
+ type: Input
1721
+ }], sortMode: [{
1722
+ type: Input
1723
+ }], customSortComparator: [{
1724
+ type: Input
1725
+ }], recentlyUsedLimit: [{
1726
+ type: Input
1335
1727
  }], name: [{
1336
1728
  type: Input
1337
1729
  }], id: [{
@@ -1382,24 +1774,29 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImpo
1382
1774
  type: Output
1383
1775
  }], pin: [{
1384
1776
  type: Output
1777
+ }], bulkActionSelected: [{
1778
+ type: Output
1385
1779
  }], selectContainerRef: [{
1386
1780
  type: ViewChild,
1387
- args: ['selectContainer']
1781
+ args: ['selectContainer', { static: false }]
1388
1782
  }], searchInputRef: [{
1389
1783
  type: ViewChild,
1390
- args: ['searchInput']
1784
+ args: ['searchInput', { static: false }]
1391
1785
  }], menuElementRef: [{
1392
1786
  type: ViewChild,
1393
- args: ['menuRef']
1787
+ args: ['menuRef', { static: false }]
1394
1788
  }], virtualScrollViewport: [{
1395
1789
  type: ViewChild,
1396
- args: [CdkVirtualScrollViewport]
1790
+ args: [CdkVirtualScrollViewport, { static: false }]
1397
1791
  }], optionTemplate: [{
1398
1792
  type: ContentChild,
1399
- args: ['optionTemplate', { read: TemplateRef }]
1793
+ args: ['optionTemplate', { read: TemplateRef, static: false }]
1400
1794
  }], selectedOptionTemplate: [{
1401
1795
  type: ContentChild,
1402
- args: ['selectedOptionTemplate', { read: TemplateRef }]
1796
+ args: ['selectedOptionTemplate', { read: TemplateRef, static: false }]
1797
+ }], tagTemplate: [{
1798
+ type: ContentChild,
1799
+ args: ['tagTemplate', { read: TemplateRef, static: false }]
1403
1800
  }], handleKeydown: [{
1404
1801
  type: HostListener,
1405
1802
  args: ['keydown', ['$event']]
@@ -1408,6 +1805,32 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImpo
1408
1805
  args: ['paste', ['$event']]
1409
1806
  }] } });
1410
1807
 
1808
+ /**
1809
+ * Bulk actions for multi-select mode
1810
+ */
1811
+
1812
+ /**
1813
+ * Provides necessary CDK modules for PerfectSelectComponent.
1814
+ * Import this in your app configuration if you encounter production build issues.
1815
+ *
1816
+ * @example
1817
+ * ```typescript
1818
+ * import { providePerfectSelect } from 'angular-perfect-select';
1819
+ *
1820
+ * bootstrapApplication(AppComponent, {
1821
+ * providers: [
1822
+ * // ... other providers
1823
+ * providePerfectSelect()
1824
+ * ]
1825
+ * });
1826
+ * ```
1827
+ */
1828
+ function providePerfectSelect() {
1829
+ return makeEnvironmentProviders([
1830
+ importProvidersFrom(ScrollingModule, DragDropModule)
1831
+ ]);
1832
+ }
1833
+
1411
1834
  /*
1412
1835
  * Public API Surface of angular-perfect-select
1413
1836
  */
@@ -1417,5 +1840,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImpo
1417
1840
  * Generated bundle index. Do not edit.
1418
1841
  */
1419
1842
 
1420
- export { ClickOutsideDirective, PerfectSelectComponent, THEMES, dropdownAnimation, optionListAnimation, selectAnimations, tagAnimation };
1843
+ export { ClickOutsideDirective, DarkModeProvider, PerfectSelectComponent, THEMES, dropdownAnimation, fuzzyMatch, optionListAnimation, providePerfectSelect, selectAnimations, sortByFuzzyScore, sortOptions, tagAnimation };
1421
1844
  //# sourceMappingURL=angular-perfect-select.mjs.map