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.
- package/README.md +176 -1
- package/dist/README.md +176 -1
- package/dist/fesm2022/angular-perfect-select.mjs +450 -27
- package/dist/fesm2022/angular-perfect-select.mjs.map +1 -1
- package/dist/index.d.ts +146 -6
- package/package.json +1 -1
|
@@ -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
|
|
4
|
-
import { CommonModule } from '@angular/common';
|
|
5
|
-
import * as
|
|
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
|
|
7
|
+
import * as i5 from '@angular/cdk/scrolling';
|
|
8
8
|
import { ScrollingModule, CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
|
|
9
|
-
import * as
|
|
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
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
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
|
-
|
|
1148
|
-
|
|
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: [
|
|
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
|