angular-perfect-select 1.1.0 → 2.0.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 +271 -3
- package/dist/README.md +271 -3
- package/dist/fesm2022/angular-perfect-select.mjs +376 -10
- package/dist/fesm2022/angular-perfect-select.mjs.map +1 -1
- package/dist/index.d.ts +76 -3
- package/package.json +8 -6
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { EventEmitter, HostListener, Output, Directive, signal, computed, ViewChild, Input, Component } from '@angular/core';
|
|
3
|
-
import * as
|
|
2
|
+
import { EventEmitter, HostListener, Output, Directive, signal, computed, TemplateRef, forwardRef, ContentChild, ViewChild, Input, Component } from '@angular/core';
|
|
3
|
+
import * as i2 from '@angular/common';
|
|
4
4
|
import { CommonModule } from '@angular/common';
|
|
5
|
-
import * as
|
|
5
|
+
import * as i3 from '@angular/forms';
|
|
6
6
|
import { FormsModule, NG_VALUE_ACCESSOR } from '@angular/forms';
|
|
7
|
+
import * as i4 from '@angular/cdk/scrolling';
|
|
8
|
+
import { ScrollingModule, CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
|
|
7
9
|
import { trigger, transition, style, animate, query, stagger } from '@angular/animations';
|
|
8
10
|
import * as i1 from '@angular/platform-browser';
|
|
9
11
|
|
|
@@ -178,6 +180,34 @@ class PerfectSelectComponent {
|
|
|
178
180
|
debounceTime = 300;
|
|
179
181
|
minSearchLength = 0;
|
|
180
182
|
minSearchMessage = 'Type to search...';
|
|
183
|
+
// v1.2.0 Features - Virtual Scrolling
|
|
184
|
+
enableVirtualScroll = false;
|
|
185
|
+
virtualScrollItemSize = 40;
|
|
186
|
+
virtualScrollMinBufferPx = 200;
|
|
187
|
+
virtualScrollMaxBufferPx = 400;
|
|
188
|
+
// v1.2.0 Features - Validation States
|
|
189
|
+
validationState = 'default';
|
|
190
|
+
validationMessage = '';
|
|
191
|
+
showValidationIcon = true;
|
|
192
|
+
// v1.2.0 Features - Tooltips
|
|
193
|
+
showTooltips = false;
|
|
194
|
+
tooltipDelay = 300;
|
|
195
|
+
getOptionTooltip = (option) => option.tooltip || '';
|
|
196
|
+
// v1.2.0 Features - Recent Selections
|
|
197
|
+
showRecentSelections = false;
|
|
198
|
+
recentSelectionsLimit = 5;
|
|
199
|
+
recentSelectionsLabel = 'Recent';
|
|
200
|
+
enableRecentSelectionsPersistence = false;
|
|
201
|
+
// v1.2.0 Features - Infinite Scroll / Pagination
|
|
202
|
+
enableInfiniteScroll = false;
|
|
203
|
+
infiniteScrollThreshold = 80;
|
|
204
|
+
totalOptionsCount = null;
|
|
205
|
+
// v1.2.0 Features - Advanced Keyboard
|
|
206
|
+
enableAdvancedKeyboard = true;
|
|
207
|
+
typeAheadDelay = 500;
|
|
208
|
+
enableCopyPaste = true;
|
|
209
|
+
copyDelimiter = ', ';
|
|
210
|
+
pasteDelimiter = ',';
|
|
181
211
|
// Behavior
|
|
182
212
|
name = 'angular-perfect-select';
|
|
183
213
|
id = 'angular-perfect-select';
|
|
@@ -200,10 +230,18 @@ class PerfectSelectComponent {
|
|
|
200
230
|
createOption = new EventEmitter();
|
|
201
231
|
optionsLoaded = new EventEmitter();
|
|
202
232
|
loadError = new EventEmitter();
|
|
233
|
+
// v1.2.0 Events
|
|
234
|
+
copy = new EventEmitter();
|
|
235
|
+
paste = new EventEmitter();
|
|
236
|
+
scrollEnd = new EventEmitter();
|
|
203
237
|
// ViewChildren
|
|
204
238
|
selectContainerRef;
|
|
205
239
|
searchInputRef;
|
|
206
240
|
menuElementRef;
|
|
241
|
+
virtualScrollViewport;
|
|
242
|
+
// ContentChildren - Custom Templates
|
|
243
|
+
optionTemplate;
|
|
244
|
+
selectedOptionTemplate;
|
|
207
245
|
// Signals for reactive state
|
|
208
246
|
isOpen = signal(false, ...(ngDevMode ? [{ debugName: "isOpen" }] : []));
|
|
209
247
|
searchTerm = signal('', ...(ngDevMode ? [{ debugName: "searchTerm" }] : []));
|
|
@@ -213,6 +251,16 @@ class PerfectSelectComponent {
|
|
|
213
251
|
isLoadingAsync = signal(false, ...(ngDevMode ? [{ debugName: "isLoadingAsync" }] : []));
|
|
214
252
|
optionsCache = new Map();
|
|
215
253
|
debounceTimeout = null;
|
|
254
|
+
// v1.2.0 Signals
|
|
255
|
+
recentSelections = signal([], ...(ngDevMode ? [{ debugName: "recentSelections" }] : []));
|
|
256
|
+
typeAheadBuffer = signal('', ...(ngDevMode ? [{ debugName: "typeAheadBuffer" }] : []));
|
|
257
|
+
hoveredOptionIndex = signal(-1, ...(ngDevMode ? [{ debugName: "hoveredOptionIndex" }] : []));
|
|
258
|
+
isScrolling = signal(false, ...(ngDevMode ? [{ debugName: "isScrolling" }] : []));
|
|
259
|
+
// v1.2.0 Private state
|
|
260
|
+
typeAheadTimeout = null;
|
|
261
|
+
tooltipTimeout = null;
|
|
262
|
+
recentSelectionsStorageKey = '';
|
|
263
|
+
scrollEndTimeout = null;
|
|
216
264
|
// Computed signals
|
|
217
265
|
currentTheme = computed(() => THEMES[this.theme] || THEMES.blue, ...(ngDevMode ? [{ debugName: "currentTheme" }] : []));
|
|
218
266
|
filteredOptions = computed(() => {
|
|
@@ -324,6 +372,29 @@ class PerfectSelectComponent {
|
|
|
324
372
|
const term = this.searchTerm();
|
|
325
373
|
return this.isOpen() && term.length > 0 && term.length < this.minSearchLength;
|
|
326
374
|
}, ...(ngDevMode ? [{ debugName: "showMinSearchMessage" }] : []));
|
|
375
|
+
// v1.2.0 Computed signals
|
|
376
|
+
displayOptionsWithRecent = computed(() => {
|
|
377
|
+
if (!this.showRecentSelections || this.searchTerm()) {
|
|
378
|
+
return this.displayOptions();
|
|
379
|
+
}
|
|
380
|
+
const recent = this.recentSelections();
|
|
381
|
+
const display = this.displayOptions();
|
|
382
|
+
// Filter out recent options from display list to avoid duplicates
|
|
383
|
+
const displayWithoutRecent = display.filter(opt => !recent.some(r => this.getOptionValue(r) === this.getOptionValue(opt)));
|
|
384
|
+
// Combine recent (marked) and regular options
|
|
385
|
+
const markedRecent = recent.map(opt => ({ ...opt, __isRecent__: true }));
|
|
386
|
+
return [...markedRecent, ...displayWithoutRecent];
|
|
387
|
+
}, ...(ngDevMode ? [{ debugName: "displayOptionsWithRecent" }] : []));
|
|
388
|
+
validationIconName = computed(() => {
|
|
389
|
+
const state = this.validationState;
|
|
390
|
+
switch (state) {
|
|
391
|
+
case 'error': return 'error';
|
|
392
|
+
case 'warning': return 'warning';
|
|
393
|
+
case 'success': return 'check_circle';
|
|
394
|
+
case 'info': return 'info';
|
|
395
|
+
default: return '';
|
|
396
|
+
}
|
|
397
|
+
}, ...(ngDevMode ? [{ debugName: "validationIconName" }] : []));
|
|
327
398
|
// Helper method for template
|
|
328
399
|
getEnabledOptionsCount() {
|
|
329
400
|
return this.filteredOptions().filter(opt => !this.isOptionDisabled(opt)).length;
|
|
@@ -369,6 +440,11 @@ class PerfectSelectComponent {
|
|
|
369
440
|
if (this.loadOptions && this.defaultOptions) {
|
|
370
441
|
this.handleLoadOptions('');
|
|
371
442
|
}
|
|
443
|
+
// v1.2.0: Initialize recent selections
|
|
444
|
+
if (this.showRecentSelections) {
|
|
445
|
+
this.recentSelectionsStorageKey = `ps-recent-${this.name || this.id}`;
|
|
446
|
+
this.loadRecentSelections();
|
|
447
|
+
}
|
|
372
448
|
// Auto-focus if needed
|
|
373
449
|
if (this.autoFocus) {
|
|
374
450
|
setTimeout(() => {
|
|
@@ -383,11 +459,62 @@ class PerfectSelectComponent {
|
|
|
383
459
|
if (this.debounceTimeout) {
|
|
384
460
|
clearTimeout(this.debounceTimeout);
|
|
385
461
|
}
|
|
462
|
+
// v1.2.0: Clear new timeouts
|
|
463
|
+
if (this.typeAheadTimeout) {
|
|
464
|
+
clearTimeout(this.typeAheadTimeout);
|
|
465
|
+
}
|
|
466
|
+
if (this.tooltipTimeout) {
|
|
467
|
+
clearTimeout(this.tooltipTimeout);
|
|
468
|
+
}
|
|
469
|
+
if (this.scrollEndTimeout) {
|
|
470
|
+
clearTimeout(this.scrollEndTimeout);
|
|
471
|
+
}
|
|
386
472
|
}
|
|
387
473
|
// Keyboard Navigation
|
|
388
474
|
handleKeydown(event) {
|
|
389
475
|
if (this.isDisabled)
|
|
390
476
|
return;
|
|
477
|
+
// v1.2.0: Advanced keyboard shortcuts
|
|
478
|
+
if (this.enableAdvancedKeyboard) {
|
|
479
|
+
// Ctrl/Cmd + A: Select All
|
|
480
|
+
if ((event.ctrlKey || event.metaKey) && event.key === 'a' && this.isMulti && this.isOpen()) {
|
|
481
|
+
event.preventDefault();
|
|
482
|
+
this.selectAll();
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
// Ctrl/Cmd + C: Copy
|
|
486
|
+
if ((event.ctrlKey || event.metaKey) && event.key === 'c' && this.enableCopyPaste) {
|
|
487
|
+
if (this.selectedOptions().length > 0) {
|
|
488
|
+
event.preventDefault();
|
|
489
|
+
this.copySelectedValues();
|
|
490
|
+
}
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
// Ctrl/Cmd + V: Paste
|
|
494
|
+
if ((event.ctrlKey || event.metaKey) && event.key === 'v' && this.enableCopyPaste && this.isMulti) {
|
|
495
|
+
// Let browser handle paste, we'll catch it in the paste event
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
// Home: Jump to first option
|
|
499
|
+
if (event.key === 'Home' && this.isOpen()) {
|
|
500
|
+
event.preventDefault();
|
|
501
|
+
this.highlightedIndex.set(0);
|
|
502
|
+
this.scrollHighlightedIntoView();
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
505
|
+
// End: Jump to last option
|
|
506
|
+
if (event.key === 'End' && this.isOpen()) {
|
|
507
|
+
event.preventDefault();
|
|
508
|
+
const opts = this.displayOptions();
|
|
509
|
+
this.highlightedIndex.set(Math.max(0, opts.length - 1));
|
|
510
|
+
this.scrollHighlightedIntoView();
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
// Type-ahead: Single character typing
|
|
514
|
+
if (event.key.length === 1 && !event.ctrlKey && !event.metaKey && !event.altKey && this.isOpen()) {
|
|
515
|
+
this.handleTypeAhead(event.key);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
391
518
|
switch (event.key) {
|
|
392
519
|
case 'ArrowDown':
|
|
393
520
|
event.preventDefault();
|
|
@@ -406,7 +533,7 @@ class PerfectSelectComponent {
|
|
|
406
533
|
event.preventDefault();
|
|
407
534
|
if (this.isOpen()) {
|
|
408
535
|
const index = this.highlightedIndex();
|
|
409
|
-
const opts = this.displayOptions();
|
|
536
|
+
const opts = this.showRecentSelections ? this.displayOptionsWithRecent() : this.displayOptions();
|
|
410
537
|
if (index >= 0 && index < opts.length) {
|
|
411
538
|
this.selectOption(opts[index]);
|
|
412
539
|
}
|
|
@@ -448,6 +575,16 @@ class PerfectSelectComponent {
|
|
|
448
575
|
break;
|
|
449
576
|
}
|
|
450
577
|
}
|
|
578
|
+
// v1.2.0: Paste event handler
|
|
579
|
+
handlePaste(event) {
|
|
580
|
+
if (!this.enableCopyPaste || !this.isMulti || this.isDisabled)
|
|
581
|
+
return;
|
|
582
|
+
const pastedText = event.clipboardData?.getData('text');
|
|
583
|
+
if (!pastedText)
|
|
584
|
+
return;
|
|
585
|
+
event.preventDefault();
|
|
586
|
+
this.pasteValues(pastedText);
|
|
587
|
+
}
|
|
451
588
|
// Toggle dropdown
|
|
452
589
|
toggleDropdown() {
|
|
453
590
|
if (this.isDisabled)
|
|
@@ -514,6 +651,10 @@ class PerfectSelectComponent {
|
|
|
514
651
|
option,
|
|
515
652
|
action: exists ? 'remove-value' : 'select-option'
|
|
516
653
|
});
|
|
654
|
+
// v1.2.0: Track recent selection
|
|
655
|
+
if (!exists && this.showRecentSelections) {
|
|
656
|
+
this.addToRecentSelections(option);
|
|
657
|
+
}
|
|
517
658
|
}
|
|
518
659
|
else {
|
|
519
660
|
this.internalValue.set(optionValue);
|
|
@@ -523,6 +664,10 @@ class PerfectSelectComponent {
|
|
|
523
664
|
option,
|
|
524
665
|
action: 'select-option'
|
|
525
666
|
});
|
|
667
|
+
// v1.2.0: Track recent selection
|
|
668
|
+
if (this.showRecentSelections) {
|
|
669
|
+
this.addToRecentSelections(option);
|
|
670
|
+
}
|
|
526
671
|
}
|
|
527
672
|
if (this.closeMenuOnSelect) {
|
|
528
673
|
this.closeDropdown();
|
|
@@ -680,20 +825,179 @@ class PerfectSelectComponent {
|
|
|
680
825
|
trackByGroup(index, item) {
|
|
681
826
|
return item[0];
|
|
682
827
|
}
|
|
828
|
+
// ==================== v1.2.0 NEW METHODS ====================
|
|
829
|
+
// Copy selected values to clipboard
|
|
830
|
+
copySelectedValues() {
|
|
831
|
+
const selected = this.selectedOptions();
|
|
832
|
+
if (selected.length === 0)
|
|
833
|
+
return;
|
|
834
|
+
const values = selected.map(opt => this.getOptionLabel(opt));
|
|
835
|
+
const formattedText = values.join(this.copyDelimiter);
|
|
836
|
+
// Copy to clipboard using modern API
|
|
837
|
+
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
838
|
+
navigator.clipboard.writeText(formattedText).then(() => {
|
|
839
|
+
this.copy.emit({ values: selected.map(opt => this.getOptionValue(opt)), formattedText });
|
|
840
|
+
});
|
|
841
|
+
}
|
|
842
|
+
else {
|
|
843
|
+
// Fallback for older browsers
|
|
844
|
+
const textarea = document.createElement('textarea');
|
|
845
|
+
textarea.value = formattedText;
|
|
846
|
+
textarea.style.position = 'fixed';
|
|
847
|
+
textarea.style.opacity = '0';
|
|
848
|
+
document.body.appendChild(textarea);
|
|
849
|
+
textarea.select();
|
|
850
|
+
document.execCommand('copy');
|
|
851
|
+
document.body.removeChild(textarea);
|
|
852
|
+
this.copy.emit({ values: selected.map(opt => this.getOptionValue(opt)), formattedText });
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
// Paste values from clipboard
|
|
856
|
+
pasteValues(pastedText) {
|
|
857
|
+
const values = pastedText
|
|
858
|
+
.split(this.pasteDelimiter)
|
|
859
|
+
.map(v => v.trim())
|
|
860
|
+
.filter(v => v.length > 0);
|
|
861
|
+
if (values.length === 0)
|
|
862
|
+
return;
|
|
863
|
+
this.paste.emit({ values, pastedText });
|
|
864
|
+
// Find matching options and select them
|
|
865
|
+
const allOptions = this.internalOptions();
|
|
866
|
+
const currentValue = Array.isArray(this.internalValue()) ? [...this.internalValue()] : [];
|
|
867
|
+
values.forEach(value => {
|
|
868
|
+
const option = allOptions.find(opt => this.getOptionLabel(opt).toLowerCase() === value.toLowerCase() ||
|
|
869
|
+
String(this.getOptionValue(opt)).toLowerCase() === value.toLowerCase());
|
|
870
|
+
if (option && !currentValue.includes(this.getOptionValue(option))) {
|
|
871
|
+
// Check max selection limit
|
|
872
|
+
if (this.maxSelectedOptions === null || currentValue.length < this.maxSelectedOptions) {
|
|
873
|
+
currentValue.push(this.getOptionValue(option));
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
});
|
|
877
|
+
this.internalValue.set(currentValue);
|
|
878
|
+
this.onChange(currentValue);
|
|
879
|
+
this.change.emit({
|
|
880
|
+
value: currentValue,
|
|
881
|
+
action: 'set-value'
|
|
882
|
+
});
|
|
883
|
+
}
|
|
884
|
+
// Type-ahead functionality
|
|
885
|
+
handleTypeAhead(key) {
|
|
886
|
+
// Clear previous timeout
|
|
887
|
+
if (this.typeAheadTimeout) {
|
|
888
|
+
clearTimeout(this.typeAheadTimeout);
|
|
889
|
+
}
|
|
890
|
+
// Append key to buffer
|
|
891
|
+
const newBuffer = this.typeAheadBuffer() + key.toLowerCase();
|
|
892
|
+
this.typeAheadBuffer.set(newBuffer);
|
|
893
|
+
// Find matching option
|
|
894
|
+
const opts = this.displayOptions();
|
|
895
|
+
const matchIndex = opts.findIndex(opt => this.getOptionLabel(opt).toLowerCase().startsWith(newBuffer));
|
|
896
|
+
if (matchIndex !== -1) {
|
|
897
|
+
this.highlightedIndex.set(matchIndex);
|
|
898
|
+
this.scrollHighlightedIntoView();
|
|
899
|
+
}
|
|
900
|
+
// Clear buffer after delay
|
|
901
|
+
this.typeAheadTimeout = setTimeout(() => {
|
|
902
|
+
this.typeAheadBuffer.set('');
|
|
903
|
+
}, this.typeAheadDelay);
|
|
904
|
+
}
|
|
905
|
+
// Recent selections management
|
|
906
|
+
addToRecentSelections(option) {
|
|
907
|
+
const recent = this.recentSelections();
|
|
908
|
+
const optionValue = this.getOptionValue(option);
|
|
909
|
+
// Remove if already exists
|
|
910
|
+
const filtered = recent.filter(r => this.getOptionValue(r) !== optionValue);
|
|
911
|
+
// Add to beginning
|
|
912
|
+
const updated = [option, ...filtered].slice(0, this.recentSelectionsLimit);
|
|
913
|
+
this.recentSelections.set(updated);
|
|
914
|
+
// Save to storage if enabled
|
|
915
|
+
if (this.enableRecentSelectionsPersistence) {
|
|
916
|
+
this.saveRecentSelections();
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
loadRecentSelections() {
|
|
920
|
+
if (!this.enableRecentSelectionsPersistence)
|
|
921
|
+
return;
|
|
922
|
+
try {
|
|
923
|
+
const stored = localStorage.getItem(this.recentSelectionsStorageKey);
|
|
924
|
+
if (stored) {
|
|
925
|
+
const parsed = JSON.parse(stored);
|
|
926
|
+
this.recentSelections.set(parsed.slice(0, this.recentSelectionsLimit));
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
catch (error) {
|
|
930
|
+
console.warn('Failed to load recent selections:', error);
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
saveRecentSelections() {
|
|
934
|
+
if (!this.enableRecentSelectionsPersistence)
|
|
935
|
+
return;
|
|
936
|
+
try {
|
|
937
|
+
const recent = this.recentSelections();
|
|
938
|
+
localStorage.setItem(this.recentSelectionsStorageKey, JSON.stringify(recent));
|
|
939
|
+
}
|
|
940
|
+
catch (error) {
|
|
941
|
+
console.warn('Failed to save recent selections:', error);
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
// Infinite scroll handler
|
|
945
|
+
onOptionsScroll(event) {
|
|
946
|
+
if (!this.enableInfiniteScroll)
|
|
947
|
+
return;
|
|
948
|
+
const target = event.target;
|
|
949
|
+
const scrollTop = target.scrollTop;
|
|
950
|
+
const scrollHeight = target.scrollHeight;
|
|
951
|
+
const clientHeight = target.clientHeight;
|
|
952
|
+
const scrollPercentage = ((scrollTop + clientHeight) / scrollHeight) * 100;
|
|
953
|
+
// Clear previous timeout
|
|
954
|
+
if (this.scrollEndTimeout) {
|
|
955
|
+
clearTimeout(this.scrollEndTimeout);
|
|
956
|
+
}
|
|
957
|
+
// Debounce scroll end detection
|
|
958
|
+
this.scrollEndTimeout = setTimeout(() => {
|
|
959
|
+
if (scrollPercentage >= this.infiniteScrollThreshold) {
|
|
960
|
+
this.scrollEnd.emit({ scrollTop, scrollHeight, clientHeight });
|
|
961
|
+
}
|
|
962
|
+
}, 100);
|
|
963
|
+
}
|
|
964
|
+
// Virtual scroll index tracking
|
|
965
|
+
onVirtualScrollIndexChange(index) {
|
|
966
|
+
// Update highlighted index for virtual scroll
|
|
967
|
+
if (this.enableVirtualScroll) {
|
|
968
|
+
this.highlightedIndex.set(index);
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
// Tooltip methods
|
|
972
|
+
showTooltip(option, index) {
|
|
973
|
+
if (!this.showTooltips)
|
|
974
|
+
return;
|
|
975
|
+
this.hoveredOptionIndex.set(index);
|
|
976
|
+
}
|
|
977
|
+
hideTooltip() {
|
|
978
|
+
this.hoveredOptionIndex.set(-1);
|
|
979
|
+
}
|
|
980
|
+
getTooltipContent(option) {
|
|
981
|
+
return this.getOptionTooltip(option) || option.tooltip || '';
|
|
982
|
+
}
|
|
983
|
+
// Validation state CSS class
|
|
984
|
+
getValidationClass() {
|
|
985
|
+
return `validation-${this.validationState}`;
|
|
986
|
+
}
|
|
683
987
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: PerfectSelectComponent, deps: [{ token: i1.DomSanitizer }], target: i0.ɵɵFactoryTarget.Component });
|
|
684
|
-
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", 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" }, host: { listeners: { "keydown": "handleKeydown($event)" } }, providers: [{
|
|
988
|
+
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", 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" }, host: { listeners: { "keydown": "handleKeydown($event)", "paste": "handlePaste($event)" } }, providers: [{
|
|
685
989
|
provide: NG_VALUE_ACCESSOR,
|
|
686
|
-
useExisting: PerfectSelectComponent,
|
|
990
|
+
useExisting: forwardRef(() => PerfectSelectComponent),
|
|
687
991
|
multi: true
|
|
688
|
-
}], 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 }], usesOnChanges: true, ngImport: i0, template: "<div\n #selectContainer\n class=\"select-container {{selectSize}} {{containerSize}} theme-{{theme}}\"\n [class.disabled]=\"isDisabled\"\n [class.rtl]=\"isRtl\"\n (clickOutside)=\"onClickOutside()\"\n role=\"combobox\"\n tabindex=\"0\"\n [attr.aria-controls]=\"'options-list'\"\n [attr.aria-expanded]=\"isOpen()\"\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 <div class=\"tags\">\n @for (option of selectedOptions(); 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 } @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 class=\"options-list\" id=\"options-list\">\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\">{{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 <!-- 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 <!-- Regular (Ungrouped) Options -->\n @for (option of 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 (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\">{{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 <!-- 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 }\n }\n </div>\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}.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}.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}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: ClickOutsideDirective, selector: "[clickOutside]", outputs: ["clickOutside"] }, { kind: "pipe", type: i3.KeyValuePipe, name: "keyvalue" }], animations: selectAnimations });
|
|
992
|
+
}], 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 <div class=\"tags\">\n @for (option of selectedOptions(); 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 } @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\">{{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 <!-- 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\">{{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 @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\">{{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 @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}.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}.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: "pipe", type: i2.KeyValuePipe, name: "keyvalue" }], animations: selectAnimations });
|
|
689
993
|
}
|
|
690
994
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: PerfectSelectComponent, decorators: [{
|
|
691
995
|
type: Component,
|
|
692
|
-
args: [{ selector: 'ng-perfect-select', standalone: true, imports: [CommonModule, FormsModule, ClickOutsideDirective], animations: selectAnimations, providers: [{
|
|
996
|
+
args: [{ selector: 'ng-perfect-select', standalone: true, imports: [CommonModule, FormsModule, ClickOutsideDirective, ScrollingModule], animations: selectAnimations, providers: [{
|
|
693
997
|
provide: NG_VALUE_ACCESSOR,
|
|
694
|
-
useExisting: PerfectSelectComponent,
|
|
998
|
+
useExisting: forwardRef(() => PerfectSelectComponent),
|
|
695
999
|
multi: true
|
|
696
|
-
}], template: "<div\n #selectContainer\n class=\"select-container {{selectSize}} {{containerSize}} theme-{{theme}}\"\n [class.disabled]=\"isDisabled\"\n [class.rtl]=\"isRtl\"\n (clickOutside)=\"onClickOutside()\"\n role=\"combobox\"\n tabindex=\"0\"\n [attr.aria-controls]=\"'options-list'\"\n [attr.aria-expanded]=\"isOpen()\"\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 <div class=\"tags\">\n @for (option of selectedOptions(); 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 } @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 class=\"options-list\" id=\"options-list\">\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\">{{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 <!-- 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 <!-- Regular (Ungrouped) Options -->\n @for (option of 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 (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\">{{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 <!-- 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 }\n }\n </div>\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}.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}.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}\n"] }]
|
|
1000
|
+
}], 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 <div class=\"tags\">\n @for (option of selectedOptions(); 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 } @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\">{{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 <!-- 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\">{{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 @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\">{{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 @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}.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}.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"] }]
|
|
697
1001
|
}], ctorParameters: () => [{ type: i1.DomSanitizer }], propDecorators: { options: [{
|
|
698
1002
|
type: Input
|
|
699
1003
|
}], placeholder: [{
|
|
@@ -794,6 +1098,50 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImpo
|
|
|
794
1098
|
type: Input
|
|
795
1099
|
}], minSearchMessage: [{
|
|
796
1100
|
type: Input
|
|
1101
|
+
}], enableVirtualScroll: [{
|
|
1102
|
+
type: Input
|
|
1103
|
+
}], virtualScrollItemSize: [{
|
|
1104
|
+
type: Input
|
|
1105
|
+
}], virtualScrollMinBufferPx: [{
|
|
1106
|
+
type: Input
|
|
1107
|
+
}], virtualScrollMaxBufferPx: [{
|
|
1108
|
+
type: Input
|
|
1109
|
+
}], validationState: [{
|
|
1110
|
+
type: Input
|
|
1111
|
+
}], validationMessage: [{
|
|
1112
|
+
type: Input
|
|
1113
|
+
}], showValidationIcon: [{
|
|
1114
|
+
type: Input
|
|
1115
|
+
}], showTooltips: [{
|
|
1116
|
+
type: Input
|
|
1117
|
+
}], tooltipDelay: [{
|
|
1118
|
+
type: Input
|
|
1119
|
+
}], getOptionTooltip: [{
|
|
1120
|
+
type: Input
|
|
1121
|
+
}], showRecentSelections: [{
|
|
1122
|
+
type: Input
|
|
1123
|
+
}], recentSelectionsLimit: [{
|
|
1124
|
+
type: Input
|
|
1125
|
+
}], recentSelectionsLabel: [{
|
|
1126
|
+
type: Input
|
|
1127
|
+
}], enableRecentSelectionsPersistence: [{
|
|
1128
|
+
type: Input
|
|
1129
|
+
}], enableInfiniteScroll: [{
|
|
1130
|
+
type: Input
|
|
1131
|
+
}], infiniteScrollThreshold: [{
|
|
1132
|
+
type: Input
|
|
1133
|
+
}], totalOptionsCount: [{
|
|
1134
|
+
type: Input
|
|
1135
|
+
}], enableAdvancedKeyboard: [{
|
|
1136
|
+
type: Input
|
|
1137
|
+
}], typeAheadDelay: [{
|
|
1138
|
+
type: Input
|
|
1139
|
+
}], enableCopyPaste: [{
|
|
1140
|
+
type: Input
|
|
1141
|
+
}], copyDelimiter: [{
|
|
1142
|
+
type: Input
|
|
1143
|
+
}], pasteDelimiter: [{
|
|
1144
|
+
type: Input
|
|
797
1145
|
}], name: [{
|
|
798
1146
|
type: Input
|
|
799
1147
|
}], id: [{
|
|
@@ -834,6 +1182,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImpo
|
|
|
834
1182
|
type: Output
|
|
835
1183
|
}], loadError: [{
|
|
836
1184
|
type: Output
|
|
1185
|
+
}], copy: [{
|
|
1186
|
+
type: Output
|
|
1187
|
+
}], paste: [{
|
|
1188
|
+
type: Output
|
|
1189
|
+
}], scrollEnd: [{
|
|
1190
|
+
type: Output
|
|
837
1191
|
}], selectContainerRef: [{
|
|
838
1192
|
type: ViewChild,
|
|
839
1193
|
args: ['selectContainer']
|
|
@@ -843,9 +1197,21 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImpo
|
|
|
843
1197
|
}], menuElementRef: [{
|
|
844
1198
|
type: ViewChild,
|
|
845
1199
|
args: ['menuRef']
|
|
1200
|
+
}], virtualScrollViewport: [{
|
|
1201
|
+
type: ViewChild,
|
|
1202
|
+
args: [CdkVirtualScrollViewport]
|
|
1203
|
+
}], optionTemplate: [{
|
|
1204
|
+
type: ContentChild,
|
|
1205
|
+
args: ['optionTemplate', { read: TemplateRef }]
|
|
1206
|
+
}], selectedOptionTemplate: [{
|
|
1207
|
+
type: ContentChild,
|
|
1208
|
+
args: ['selectedOptionTemplate', { read: TemplateRef }]
|
|
846
1209
|
}], handleKeydown: [{
|
|
847
1210
|
type: HostListener,
|
|
848
1211
|
args: ['keydown', ['$event']]
|
|
1212
|
+
}], handlePaste: [{
|
|
1213
|
+
type: HostListener,
|
|
1214
|
+
args: ['paste', ['$event']]
|
|
849
1215
|
}] } });
|
|
850
1216
|
|
|
851
1217
|
/*
|