ngx-com 0.0.21 → 0.1.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.
Files changed (114) hide show
  1. package/README.md +137 -33
  2. package/fesm2022/ngx-com-components-alert.mjs +21 -11
  3. package/fesm2022/ngx-com-components-alert.mjs.map +1 -1
  4. package/fesm2022/ngx-com-components-avatar.mjs +9 -7
  5. package/fesm2022/ngx-com-components-avatar.mjs.map +1 -1
  6. package/fesm2022/ngx-com-components-button.mjs +1 -1
  7. package/fesm2022/ngx-com-components-button.mjs.map +1 -1
  8. package/fesm2022/ngx-com-components-calendar.mjs +27 -3112
  9. package/fesm2022/ngx-com-components-calendar.mjs.map +1 -1
  10. package/fesm2022/ngx-com-components-card.mjs +8 -8
  11. package/fesm2022/ngx-com-components-card.mjs.map +1 -1
  12. package/fesm2022/ngx-com-components-carousel.mjs +16 -4
  13. package/fesm2022/ngx-com-components-carousel.mjs.map +1 -1
  14. package/fesm2022/ngx-com-components-checkbox.mjs +1 -1
  15. package/fesm2022/ngx-com-components-checkbox.mjs.map +1 -1
  16. package/fesm2022/ngx-com-components-code-block.mjs +9 -9
  17. package/fesm2022/ngx-com-components-code-block.mjs.map +1 -1
  18. package/fesm2022/ngx-com-components-collapsible.mjs +15 -13
  19. package/fesm2022/ngx-com-components-collapsible.mjs.map +1 -1
  20. package/fesm2022/ngx-com-components-confirm.mjs +4 -4
  21. package/fesm2022/ngx-com-components-confirm.mjs.map +1 -1
  22. package/fesm2022/ngx-com-components-datepicker.mjs +2334 -0
  23. package/fesm2022/ngx-com-components-datepicker.mjs.map +1 -0
  24. package/fesm2022/ngx-com-components-dialog.mjs +47 -45
  25. package/fesm2022/ngx-com-components-dialog.mjs.map +1 -1
  26. package/fesm2022/ngx-com-components-dropdown.mjs +446 -340
  27. package/fesm2022/ngx-com-components-dropdown.mjs.map +1 -1
  28. package/fesm2022/ngx-com-components-empty-state.mjs +5 -3
  29. package/fesm2022/ngx-com-components-empty-state.mjs.map +1 -1
  30. package/fesm2022/ngx-com-components-form-field.mjs +11 -6
  31. package/fesm2022/ngx-com-components-form-field.mjs.map +1 -1
  32. package/fesm2022/ngx-com-components-icon-lucide.mjs +41 -0
  33. package/fesm2022/ngx-com-components-icon-lucide.mjs.map +1 -0
  34. package/fesm2022/ngx-com-components-icon.mjs +89 -61
  35. package/fesm2022/ngx-com-components-icon.mjs.map +1 -1
  36. package/fesm2022/ngx-com-components-item.mjs +14 -4
  37. package/fesm2022/ngx-com-components-item.mjs.map +1 -1
  38. package/fesm2022/ngx-com-components-menu.mjs +61 -69
  39. package/fesm2022/ngx-com-components-menu.mjs.map +1 -1
  40. package/fesm2022/ngx-com-components-native-control.mjs +170 -0
  41. package/fesm2022/ngx-com-components-native-control.mjs.map +1 -0
  42. package/fesm2022/ngx-com-components-paginator.mjs +11 -3
  43. package/fesm2022/ngx-com-components-paginator.mjs.map +1 -1
  44. package/fesm2022/ngx-com-components-popover.mjs +58 -33
  45. package/fesm2022/ngx-com-components-popover.mjs.map +1 -1
  46. package/fesm2022/ngx-com-components-radio.mjs +4 -4
  47. package/fesm2022/ngx-com-components-radio.mjs.map +1 -1
  48. package/fesm2022/ngx-com-components-segmented-control.mjs +6 -4
  49. package/fesm2022/ngx-com-components-segmented-control.mjs.map +1 -1
  50. package/fesm2022/ngx-com-components-sort.mjs +63 -57
  51. package/fesm2022/ngx-com-components-sort.mjs.map +1 -1
  52. package/fesm2022/ngx-com-components-spinner.mjs +6 -6
  53. package/fesm2022/ngx-com-components-spinner.mjs.map +1 -1
  54. package/fesm2022/ngx-com-components-switch.mjs +18 -9
  55. package/fesm2022/ngx-com-components-switch.mjs.map +1 -1
  56. package/fesm2022/ngx-com-components-table.mjs +23 -9
  57. package/fesm2022/ngx-com-components-table.mjs.map +1 -1
  58. package/fesm2022/ngx-com-components-tabs.mjs +81 -58
  59. package/fesm2022/ngx-com-components-tabs.mjs.map +1 -1
  60. package/fesm2022/ngx-com-components-timepicker.mjs +1048 -0
  61. package/fesm2022/ngx-com-components-timepicker.mjs.map +1 -0
  62. package/fesm2022/ngx-com-components-toast.mjs +18 -14
  63. package/fesm2022/ngx-com-components-toast.mjs.map +1 -1
  64. package/fesm2022/ngx-com-components-tooltip.mjs +5 -5
  65. package/fesm2022/ngx-com-components-tooltip.mjs.map +1 -1
  66. package/fesm2022/ngx-com-components.mjs +0 -13
  67. package/fesm2022/ngx-com-components.mjs.map +1 -1
  68. package/fesm2022/ngx-com-tokens.mjs +0 -8
  69. package/fesm2022/ngx-com-tokens.mjs.map +1 -1
  70. package/fesm2022/ngx-com-utils.mjs +13 -1
  71. package/fesm2022/ngx-com-utils.mjs.map +1 -1
  72. package/fesm2022/ngx-com.mjs +1 -1
  73. package/fesm2022/ngx-com.mjs.map +1 -1
  74. package/package.json +51 -8
  75. package/styles/animations.css +38 -0
  76. package/styles/candy.css +121 -0
  77. package/styles/dark.css +159 -0
  78. package/styles/forest.css +117 -0
  79. package/styles/ocean.css +117 -0
  80. package/styles/themes.css +7 -0
  81. package/styles/tokens.css +277 -0
  82. package/styles/utilities.css +16 -0
  83. package/types/ngx-com-components-alert.d.ts +14 -4
  84. package/types/ngx-com-components-avatar.d.ts +2 -0
  85. package/types/ngx-com-components-calendar.d.ts +3 -747
  86. package/types/ngx-com-components-card.d.ts +2 -2
  87. package/types/ngx-com-components-carousel.d.ts +11 -1
  88. package/types/ngx-com-components-code-block.d.ts +4 -4
  89. package/types/ngx-com-components-collapsible.d.ts +10 -2
  90. package/types/ngx-com-components-confirm.d.ts +2 -2
  91. package/types/ngx-com-components-datepicker.d.ts +623 -0
  92. package/types/ngx-com-components-dialog.d.ts +5 -2
  93. package/types/ngx-com-components-dropdown.d.ts +22 -4
  94. package/types/ngx-com-components-empty-state.d.ts +2 -0
  95. package/types/ngx-com-components-form-field.d.ts +4 -1
  96. package/types/ngx-com-components-icon-lucide.d.ts +32 -0
  97. package/types/ngx-com-components-icon.d.ts +49 -35
  98. package/types/ngx-com-components-item.d.ts +12 -2
  99. package/types/ngx-com-components-menu.d.ts +38 -38
  100. package/types/ngx-com-components-native-control.d.ts +99 -0
  101. package/types/ngx-com-components-paginator.d.ts +2 -0
  102. package/types/ngx-com-components-popover.d.ts +19 -12
  103. package/types/ngx-com-components-segmented-control.d.ts +3 -1
  104. package/types/ngx-com-components-sort.d.ts +13 -10
  105. package/types/ngx-com-components-switch.d.ts +7 -2
  106. package/types/ngx-com-components-table.d.ts +16 -2
  107. package/types/ngx-com-components-tabs.d.ts +46 -34
  108. package/types/ngx-com-components-timepicker.d.ts +273 -0
  109. package/types/ngx-com-components-toast.d.ts +4 -2
  110. package/types/ngx-com-components-tooltip.d.ts +1 -1
  111. package/types/ngx-com-components.d.ts +6 -7
  112. package/types/ngx-com-tokens.d.ts +5 -3
  113. package/types/ngx-com-utils.d.ts +11 -1
  114. package/types/ngx-com.d.ts +1 -1
@@ -4,12 +4,13 @@ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
4
4
  import { NgTemplateOutlet } from '@angular/common';
5
5
  import { NgForm, FormGroupDirective, NgControl } from '@angular/forms';
6
6
  import { ErrorStateMatcher, FormFieldControl } from 'ngx-com/components/form-field';
7
+ import * as i1 from '@angular/cdk/overlay';
7
8
  import { Overlay, OverlayModule } from '@angular/cdk/overlay';
9
+ import { CdkVirtualScrollViewport, CdkFixedSizeVirtualScroll, CdkVirtualForOf } from '@angular/cdk/scrolling';
8
10
  import { TemplatePortal } from '@angular/cdk/portal';
9
11
  import { LiveAnnouncer } from '@angular/cdk/a11y';
10
12
  import { cva } from 'class-variance-authority';
11
13
  import { mergeClasses } from 'ngx-com/utils';
12
- import { CdkVirtualScrollViewport, CdkFixedSizeVirtualScroll, CdkVirtualForOf } from '@angular/cdk/scrolling';
13
14
  import { Subject } from 'rxjs';
14
15
  import { debounceTime, distinctUntilChanged } from 'rxjs/operators';
15
16
 
@@ -31,7 +32,7 @@ const dropdownTriggerVariants = cva([
31
32
  'bg-input-background',
32
33
  'text-input-foreground',
33
34
  'transition-colors',
34
- 'duration-150',
35
+ 'duration-normal',
35
36
  'placeholder:text-input-placeholder',
36
37
  'focus-visible:outline-[1px]',
37
38
  'focus-visible:outline-offset-2',
@@ -130,7 +131,7 @@ const dropdownTriggerVariants = cva([
130
131
  /**
131
132
  * CVA variants for the dropdown panel (overlay).
132
133
  *
133
- * @tokens `--color-popover`, `--color-popover-foreground`, `--color-border-subtle`, `--radius-overlay`
134
+ * @tokens `--color-popover`, `--color-popover-foreground`, `--color-border-subtle`, `--shadow-overlay`, `--radius-overlay`
134
135
  */
135
136
  const dropdownPanelVariants = cva([
136
137
  'z-50',
@@ -140,7 +141,7 @@ const dropdownPanelVariants = cva([
140
141
  'border-border-subtle',
141
142
  'bg-popover',
142
143
  'text-popover-foreground',
143
- 'shadow-lg',
144
+ 'shadow-overlay',
144
145
  'outline-none',
145
146
  ], {
146
147
  variants: {
@@ -169,7 +170,7 @@ const dropdownOptionVariants = cva([
169
170
  'items-center',
170
171
  'outline-none',
171
172
  'transition-colors',
172
- 'duration-100',
173
+ 'duration-fast',
173
174
  ], {
174
175
  variants: {
175
176
  size: {
@@ -251,7 +252,7 @@ const dropdownTagVariants = cva([
251
252
  'rounded-tag',
252
253
  'font-medium',
253
254
  'transition-colors',
254
- 'duration-100',
255
+ 'duration-fast',
255
256
  ], {
256
257
  variants: {
257
258
  size: {
@@ -418,7 +419,7 @@ const dropdownChevronVariants = cva([
418
419
  'shrink-0',
419
420
  'text-muted-foreground',
420
421
  'transition-transform',
421
- 'duration-200',
422
+ 'duration-slow',
422
423
  ], {
423
424
  variants: {
424
425
  size: {
@@ -478,6 +479,7 @@ class ComDropdownOption {
478
479
  /** Additional CSS classes to apply. */
479
480
  userClass = input('', { ...(ngDevMode ? { debugName: "userClass" } : {}), alias: 'class' });
480
481
  /** Emitted when the option is selected. */
482
+ // eslint-disable-next-line @angular-eslint/no-output-native -- intentional: matches listbox/combobox select pattern
481
483
  select = output();
482
484
  /** Emitted when the mouse enters the option. */
483
485
  hover = output();
@@ -545,7 +547,10 @@ class ComDropdownOption {
545
547
  [attr.data-active]="active() || null"
546
548
  [attr.data-selected]="selected() || null"
547
549
  [attr.data-disabled]="disabled() || null"
550
+ tabindex="-1"
548
551
  (click)="onOptionClick($event)"
552
+ (keydown.enter)="onOptionClick($event)"
553
+ (keydown.space)="onOptionClick($event)"
549
554
  (mouseenter)="onMouseEnter()"
550
555
  >
551
556
  @if (optionTemplate()) {
@@ -588,7 +593,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
588
593
  [attr.data-active]="active() || null"
589
594
  [attr.data-selected]="selected() || null"
590
595
  [attr.data-disabled]="disabled() || null"
596
+ tabindex="-1"
591
597
  (click)="onOptionClick($event)"
598
+ (keydown.enter)="onOptionClick($event)"
599
+ (keydown.space)="onOptionClick($event)"
592
600
  (mouseenter)="onMouseEnter()"
593
601
  >
594
602
  @if (optionTemplate()) {
@@ -622,228 +630,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
622
630
  }]
623
631
  }], propDecorators: { optionRef: [{ type: i0.ViewChild, args: ['optionElement', { isSignal: true }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: true }] }], displayText: [{ type: i0.Input, args: [{ isSignal: true, alias: "displayText", required: false }] }], id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: true }] }], index: [{ type: i0.Input, args: [{ isSignal: true, alias: "index", required: false }] }], selected: [{ type: i0.Input, args: [{ isSignal: true, alias: "selected", required: false }] }], active: [{ type: i0.Input, args: [{ isSignal: true, alias: "active", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], optionTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "optionTemplate", required: false }] }], userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }], select: [{ type: i0.Output, args: ["select"] }], hover: [{ type: i0.Output, args: ["hover"] }] } });
624
632
 
625
- /**
626
- * The overlay panel containing the dropdown options.
627
- * Supports virtual scrolling for large lists.
628
- *
629
- * @example
630
- * ```html
631
- * <com-dropdown-panel
632
- * [options]="filteredOptions()"
633
- * [maxHeight]="'300px'"
634
- * [virtualScrollEnabled]="true"
635
- * >
636
- * <ng-content />
637
- * </com-dropdown-panel>
638
- * ```
639
- *
640
- * @tokens `--color-popover`, `--color-popover-foreground`, `--color-border-subtle`,
641
- * `--color-muted-foreground`, `--radius-overlay`
642
- */
643
- class ComDropdownPanel {
644
- /** Reference to the panel element. */
645
- panelRef = viewChild('panelElement', ...(ngDevMode ? [{ debugName: "panelRef" }] : []));
646
- /** Reference to the virtual scroll viewport (when enabled). */
647
- viewport = viewChild('viewport', ...(ngDevMode ? [{ debugName: "viewport" }] : []));
648
- /** Unique identifier for the panel. */
649
- panelId = input('', ...(ngDevMode ? [{ debugName: "panelId" }] : []));
650
- /** The processed options to display. */
651
- options = input([], ...(ngDevMode ? [{ debugName: "options" }] : []));
652
- /** Maximum height of the panel. */
653
- maxHeight = input('256px', ...(ngDevMode ? [{ debugName: "maxHeight" }] : []));
654
- /** Whether multiple selection is enabled. */
655
- multiselectable = input(false, ...(ngDevMode ? [{ debugName: "multiselectable" }] : []));
656
- /** Size variant for styling. */
657
- size = input('default', ...(ngDevMode ? [{ debugName: "size" }] : []));
658
- /** Additional CSS classes to apply to the panel. */
659
- panelClass = input('', ...(ngDevMode ? [{ debugName: "panelClass" }] : []));
660
- /** Whether virtual scrolling is enabled. */
661
- virtualScrollEnabled = input(false, ...(ngDevMode ? [{ debugName: "virtualScrollEnabled" }] : []));
662
- /** Item size for virtual scrolling (in pixels). */
663
- itemSize = input(40, ...(ngDevMode ? [{ debugName: "itemSize" }] : []));
664
- /** The current search query (for empty state context). */
665
- searchQuery = input('', ...(ngDevMode ? [{ debugName: "searchQuery" }] : []));
666
- /** Custom empty state text. */
667
- emptyText = input('No options available', ...(ngDevMode ? [{ debugName: "emptyText" }] : []));
668
- /** Template for rendering each option. */
669
- optionTemplate = input.required(...(ngDevMode ? [{ debugName: "optionTemplate" }] : []));
670
- /** Custom template for the empty state. */
671
- emptyTemplate = input(null, ...(ngDevMode ? [{ debugName: "emptyTemplate" }] : []));
672
- /** Emitted when the panel is scrolled. */
673
- scrolled = output();
674
- /** Whether to show the empty state. */
675
- showEmpty = computed(() => this.options().length === 0, ...(ngDevMode ? [{ debugName: "showEmpty" }] : []));
676
- /** Computed CSS classes for the panel. */
677
- panelClasses = computed(() => {
678
- const baseClasses = dropdownPanelVariants({ size: this.size() });
679
- return mergeClasses(baseClasses, this.panelClass());
680
- }, ...(ngDevMode ? [{ debugName: "panelClasses" }] : []));
681
- /** Computed CSS classes for the empty state. */
682
- emptyClasses = computed(() => {
683
- return dropdownEmptyVariants({ size: this.size() });
684
- }, ...(ngDevMode ? [{ debugName: "emptyClasses" }] : []));
685
- /** Template context for the empty state. */
686
- emptyContext = computed(() => ({
687
- $implicit: this.searchQuery(),
688
- }), ...(ngDevMode ? [{ debugName: "emptyContext" }] : []));
689
- /** Track function for options. */
690
- trackByFn(_index, option) {
691
- return option.id;
692
- }
693
- /** Scrolls to a specific index. */
694
- scrollToIndex(index) {
695
- const vp = this.viewport();
696
- if (this.virtualScrollEnabled() && vp) {
697
- vp.scrollToIndex(index);
698
- }
699
- else {
700
- const panelEl = this.panelRef()?.nativeElement;
701
- const optionEl = panelEl?.querySelector(`[data-index="${index}"]`);
702
- optionEl?.scrollIntoView({ block: 'nearest', inline: 'nearest' });
703
- }
704
- }
705
- /** Scrolls an option into view. */
706
- scrollOptionIntoView(optionElement) {
707
- optionElement.scrollIntoView({ block: 'nearest', inline: 'nearest' });
708
- }
709
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: ComDropdownPanel, deps: [], target: i0.ɵɵFactoryTarget.Component });
710
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.0", type: ComDropdownPanel, isStandalone: true, selector: "com-dropdown-panel", inputs: { panelId: { classPropertyName: "panelId", publicName: "panelId", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, maxHeight: { classPropertyName: "maxHeight", publicName: "maxHeight", isSignal: true, isRequired: false, transformFunction: null }, multiselectable: { classPropertyName: "multiselectable", publicName: "multiselectable", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, panelClass: { classPropertyName: "panelClass", publicName: "panelClass", isSignal: true, isRequired: false, transformFunction: null }, virtualScrollEnabled: { classPropertyName: "virtualScrollEnabled", publicName: "virtualScrollEnabled", isSignal: true, isRequired: false, transformFunction: null }, itemSize: { classPropertyName: "itemSize", publicName: "itemSize", isSignal: true, isRequired: false, transformFunction: null }, searchQuery: { classPropertyName: "searchQuery", publicName: "searchQuery", isSignal: true, isRequired: false, transformFunction: null }, emptyText: { classPropertyName: "emptyText", publicName: "emptyText", isSignal: true, isRequired: false, transformFunction: null }, optionTemplate: { classPropertyName: "optionTemplate", publicName: "optionTemplate", isSignal: true, isRequired: true, transformFunction: null }, emptyTemplate: { classPropertyName: "emptyTemplate", publicName: "emptyTemplate", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { scrolled: "scrolled" }, host: { classAttribute: "com-dropdown-panel-host block" }, viewQueries: [{ propertyName: "panelRef", first: true, predicate: ["panelElement"], descendants: true, isSignal: true }, { propertyName: "viewport", first: true, predicate: ["viewport"], descendants: true, isSignal: true }], exportAs: ["comDropdownPanel"], ngImport: i0, template: `
711
- <div
712
- #panelElement
713
- [class]="panelClasses()"
714
- [attr.role]="'listbox'"
715
- [attr.aria-multiselectable]="multiselectable() || null"
716
- [attr.id]="panelId()"
717
- >
718
- <!-- Search slot -->
719
- <ng-content select="[comDropdownSearch]" />
720
-
721
- <!-- Options container -->
722
- @if (virtualScrollEnabled()) {
723
- <cdk-virtual-scroll-viewport
724
- [itemSize]="itemSize()"
725
- [maxBufferPx]="400"
726
- [minBufferPx]="200"
727
- [style.height]="maxHeight()"
728
- class="overflow-auto"
729
- >
730
- <div
731
- *cdkVirtualFor="let option of options(); trackBy: trackByFn; let i = index"
732
- [attr.data-index]="i"
733
- >
734
- <ng-container
735
- [ngTemplateOutlet]="optionTemplate()"
736
- [ngTemplateOutletContext]="{ $implicit: option, index: i }"
737
- />
738
- </div>
739
- </cdk-virtual-scroll-viewport>
740
- } @else {
741
- <div
742
- class="overflow-auto"
743
- [style.maxHeight]="maxHeight()"
744
- >
745
- @for (option of options(); track option.id; let i = $index) {
746
- <ng-container
747
- [ngTemplateOutlet]="optionTemplate()"
748
- [ngTemplateOutletContext]="{ $implicit: option, index: i }"
749
- />
750
- }
751
- </div>
752
- }
753
-
754
- <!-- Empty state -->
755
- @if (showEmpty()) {
756
- @if (emptyTemplate()) {
757
- <ng-container
758
- [ngTemplateOutlet]="emptyTemplate()!"
759
- [ngTemplateOutletContext]="emptyContext()"
760
- />
761
- } @else {
762
- <div [class]="emptyClasses()">
763
- {{ emptyText() }}
764
- </div>
765
- }
766
- }
767
- </div>
768
- `, isInline: true, dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: CdkVirtualScrollViewport, selector: "cdk-virtual-scroll-viewport", inputs: ["orientation", "appendOnly"], outputs: ["scrolledIndexChange"] }, { kind: "directive", type: CdkFixedSizeVirtualScroll, selector: "cdk-virtual-scroll-viewport[itemSize]", inputs: ["itemSize", "minBufferPx", "maxBufferPx"] }, { kind: "directive", type: CdkVirtualForOf, selector: "[cdkVirtualFor][cdkVirtualForOf]", inputs: ["cdkVirtualForOf", "cdkVirtualForTrackBy", "cdkVirtualForTemplate", "cdkVirtualForTemplateCacheSize"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
769
- }
770
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: ComDropdownPanel, decorators: [{
771
- type: Component,
772
- args: [{
773
- selector: 'com-dropdown-panel',
774
- exportAs: 'comDropdownPanel',
775
- template: `
776
- <div
777
- #panelElement
778
- [class]="panelClasses()"
779
- [attr.role]="'listbox'"
780
- [attr.aria-multiselectable]="multiselectable() || null"
781
- [attr.id]="panelId()"
782
- >
783
- <!-- Search slot -->
784
- <ng-content select="[comDropdownSearch]" />
785
-
786
- <!-- Options container -->
787
- @if (virtualScrollEnabled()) {
788
- <cdk-virtual-scroll-viewport
789
- [itemSize]="itemSize()"
790
- [maxBufferPx]="400"
791
- [minBufferPx]="200"
792
- [style.height]="maxHeight()"
793
- class="overflow-auto"
794
- >
795
- <div
796
- *cdkVirtualFor="let option of options(); trackBy: trackByFn; let i = index"
797
- [attr.data-index]="i"
798
- >
799
- <ng-container
800
- [ngTemplateOutlet]="optionTemplate()"
801
- [ngTemplateOutletContext]="{ $implicit: option, index: i }"
802
- />
803
- </div>
804
- </cdk-virtual-scroll-viewport>
805
- } @else {
806
- <div
807
- class="overflow-auto"
808
- [style.maxHeight]="maxHeight()"
809
- >
810
- @for (option of options(); track option.id; let i = $index) {
811
- <ng-container
812
- [ngTemplateOutlet]="optionTemplate()"
813
- [ngTemplateOutletContext]="{ $implicit: option, index: i }"
814
- />
815
- }
816
- </div>
817
- }
818
-
819
- <!-- Empty state -->
820
- @if (showEmpty()) {
821
- @if (emptyTemplate()) {
822
- <ng-container
823
- [ngTemplateOutlet]="emptyTemplate()!"
824
- [ngTemplateOutletContext]="emptyContext()"
825
- />
826
- } @else {
827
- <div [class]="emptyClasses()">
828
- {{ emptyText() }}
829
- </div>
830
- }
831
- }
832
- </div>
833
- `,
834
- imports: [
835
- NgTemplateOutlet,
836
- CdkVirtualScrollViewport,
837
- CdkFixedSizeVirtualScroll,
838
- CdkVirtualForOf,
839
- ],
840
- changeDetection: ChangeDetectionStrategy.OnPush,
841
- host: {
842
- class: 'com-dropdown-panel-host block',
843
- },
844
- }]
845
- }], propDecorators: { panelRef: [{ type: i0.ViewChild, args: ['panelElement', { isSignal: true }] }], viewport: [{ type: i0.ViewChild, args: ['viewport', { isSignal: true }] }], panelId: [{ type: i0.Input, args: [{ isSignal: true, alias: "panelId", required: false }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], maxHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxHeight", required: false }] }], multiselectable: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiselectable", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], panelClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "panelClass", required: false }] }], virtualScrollEnabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "virtualScrollEnabled", required: false }] }], itemSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "itemSize", required: false }] }], searchQuery: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchQuery", required: false }] }], emptyText: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyText", required: false }] }], optionTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "optionTemplate", required: true }] }], emptyTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyTemplate", required: false }] }], scrolled: [{ type: i0.Output, args: ["scrolled"] }] } });
846
-
847
633
  /**
848
634
  * Search input component for filtering dropdown options.
849
635
  * Includes debouncing for better performance.
@@ -1305,6 +1091,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
1305
1091
  /**
1306
1092
  * Directive to mark a template as the custom option template.
1307
1093
  *
1094
+ * @tokens none
1095
+ *
1308
1096
  * @example
1309
1097
  * ```html
1310
1098
  * <com-dropdown [options]="users()">
@@ -1342,6 +1130,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
1342
1130
  /**
1343
1131
  * Directive to mark a template as the custom selected value template.
1344
1132
  *
1133
+ * @tokens none
1134
+ *
1345
1135
  * @example
1346
1136
  * ```html
1347
1137
  * <com-dropdown [options]="users()">
@@ -1381,6 +1171,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
1381
1171
  /**
1382
1172
  * Directive to mark a template as the custom empty state template.
1383
1173
  *
1174
+ * @tokens none
1175
+ *
1384
1176
  * @example
1385
1177
  * ```html
1386
1178
  * <com-dropdown [options]="users()" [searchable]="true">
@@ -1416,6 +1208,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
1416
1208
  /**
1417
1209
  * Directive to mark a template as the custom group header template.
1418
1210
  *
1211
+ * @tokens none
1212
+ *
1419
1213
  * @example
1420
1214
  * ```html
1421
1215
  * <com-dropdown [options]="users()" [groupBy]="groupByDepartment">
@@ -1451,6 +1245,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
1451
1245
  /**
1452
1246
  * Directive to mark a template as the custom tag template (multi-select mode).
1453
1247
  *
1248
+ * @tokens none
1249
+ *
1454
1250
  * @example
1455
1251
  * ```html
1456
1252
  * <com-dropdown [options]="users()" [multiple]="true">
@@ -1584,16 +1380,18 @@ class ComDropdown {
1584
1380
  triggerRef = viewChild.required('triggerElement');
1585
1381
  /** Reference to the panel template. */
1586
1382
  panelTemplateRef = viewChild.required('panelTemplate');
1383
+ /** Reference to the virtual scroll viewport (when enabled). */
1384
+ virtualViewport = viewChild(CdkVirtualScrollViewport, ...(ngDevMode ? [{ debugName: "virtualViewport" }] : []));
1587
1385
  /** Content query for custom option template. */
1588
- optionTemplate = contentChild((ComDropdownOptionTpl), ...(ngDevMode ? [{ debugName: "optionTemplate" }] : []));
1386
+ optionTemplate = contentChild(ComDropdownOptionTpl, ...(ngDevMode ? [{ debugName: "optionTemplate" }] : []));
1589
1387
  /** Content query for custom selected template. */
1590
- selectedTemplate = contentChild((ComDropdownSelectedTpl), ...(ngDevMode ? [{ debugName: "selectedTemplate" }] : []));
1388
+ selectedTemplate = contentChild(ComDropdownSelectedTpl, ...(ngDevMode ? [{ debugName: "selectedTemplate" }] : []));
1591
1389
  /** Content query for custom empty template. */
1592
1390
  emptyTemplate = contentChild(ComDropdownEmptyTpl, ...(ngDevMode ? [{ debugName: "emptyTemplate" }] : []));
1593
1391
  /** Content query for custom group template. */
1594
1392
  groupTemplate = contentChild(ComDropdownGroupTpl, ...(ngDevMode ? [{ debugName: "groupTemplate" }] : []));
1595
1393
  /** Content query for custom tag template. */
1596
- tagTemplate = contentChild((ComDropdownTagTpl), ...(ngDevMode ? [{ debugName: "tagTemplate" }] : []));
1394
+ tagTemplate = contentChild(ComDropdownTagTpl, ...(ngDevMode ? [{ debugName: "tagTemplate" }] : []));
1597
1395
  /** Overlay reference. */
1598
1396
  overlayRef = null;
1599
1397
  /** Unique ID for the dropdown. */
@@ -1612,7 +1410,7 @@ class ComDropdown {
1612
1410
  /** Search input placeholder. */
1613
1411
  searchPlaceholder = input('Search...', ...(ngDevMode ? [{ debugName: "searchPlaceholder" }] : []));
1614
1412
  /** Disable the dropdown. */
1615
- disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
1413
+ disabled = model(false, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
1616
1414
  /** Mark as required. */
1617
1415
  required = input(false, ...(ngDevMode ? [{ debugName: "required" }] : []));
1618
1416
  /** Show clear button. */
@@ -1643,6 +1441,8 @@ class ComDropdown {
1643
1441
  searchDebounceMs = input(300, ...(ngDevMode ? [{ debugName: "searchDebounceMs" }] : []));
1644
1442
  /** Virtual scroll threshold. */
1645
1443
  virtualScrollThreshold = input(VIRTUAL_SCROLL_THRESHOLD, ...(ngDevMode ? [{ debugName: "virtualScrollThreshold" }] : []));
1444
+ /** Item size in pixels for virtual scrolling. */
1445
+ virtualScrollItemSize = input(40, ...(ngDevMode ? [{ debugName: "virtualScrollItemSize" }] : []));
1646
1446
  /** Maximum number of tags to display in multi-select mode. Set to null for no limit. */
1647
1447
  maxVisibleTags = input(2, ...(ngDevMode ? [{ debugName: "maxVisibleTags" }] : []));
1648
1448
  /** Custom error state matcher for determining when to show errors. */
@@ -1838,7 +1638,7 @@ class ComDropdown {
1838
1638
  }, ...(ngDevMode ? [{ debugName: "triggerClasses" }] : []));
1839
1639
  /** Computed panel classes. */
1840
1640
  panelClasses = computed(() => {
1841
- return mergeClasses('w-full z-50 overflow-hidden rounded-overlay border border-border-subtle bg-popover text-popover-foreground shadow-lg outline-none', this.panelClass());
1641
+ return mergeClasses('w-full z-50 overflow-hidden rounded-overlay border border-border-subtle bg-popover text-popover-foreground shadow-overlay outline-none', this.panelClass());
1842
1642
  }, ...(ngDevMode ? [{ debugName: "panelClasses" }] : []));
1843
1643
  /** Computed chevron classes. */
1844
1644
  chevronClasses = computed(() => {
@@ -1874,8 +1674,7 @@ class ComDropdown {
1874
1674
  this.onTouched = fn;
1875
1675
  }
1876
1676
  setDisabledState(isDisabled) {
1877
- // Disabled state is handled via the disabled input
1878
- // When using forms, the form control's disabled state takes precedence
1677
+ this.disabled.set(isDisabled);
1879
1678
  }
1880
1679
  // ============ FormFieldControl IMPLEMENTATION ============
1881
1680
  /**
@@ -2042,6 +1841,9 @@ class ComDropdown {
2042
1841
  break;
2043
1842
  case 'Enter':
2044
1843
  case ' ':
1844
+ if (event.key === ' ' && this.searchable() && event.target instanceof HTMLInputElement) {
1845
+ return;
1846
+ }
2045
1847
  event.preventDefault();
2046
1848
  this.selectActiveOption();
2047
1849
  break;
@@ -2109,6 +1911,10 @@ class ComDropdown {
2109
1911
  trackByValue(item, _index) {
2110
1912
  return item;
2111
1913
  }
1914
+ /** Track function for virtual scroll options. */
1915
+ trackByOption(_index, option) {
1916
+ return option.id;
1917
+ }
2112
1918
  getGlobalIndex(groupKey, localIndex) {
2113
1919
  const groups = this.groupedOptions();
2114
1920
  let globalIndex = 0;
@@ -2132,7 +1938,7 @@ class ComDropdown {
2132
1938
  .withPositions(DEFAULT_POSITIONS)
2133
1939
  .withFlexibleDimensions(false)
2134
1940
  .withPush(true);
2135
- const hostWidth = hostEl.getBoundingClientRect().width;
1941
+ const hostWidth = hostEl.getBoundingClientRect?.()?.width ?? 0;
2136
1942
  this.overlayRef = this.overlay.create({
2137
1943
  positionStrategy,
2138
1944
  scrollStrategy: this.overlay.scrollStrategies.reposition(),
@@ -2233,20 +2039,37 @@ class ComDropdown {
2233
2039
  const targetOption = options[nextIndex];
2234
2040
  if (targetOption) {
2235
2041
  this.activeOptionId.set(targetOption.id);
2042
+ this.scrollActiveOptionIntoView(nextIndex, targetOption.id);
2236
2043
  }
2237
2044
  }
2238
2045
  navigateToFirst() {
2239
2046
  const options = this.groupBy() ? this.flattenGroupedOptions() : this.filteredOptions();
2240
2047
  const firstEnabled = options.find((opt) => !opt.disabled);
2241
2048
  if (firstEnabled) {
2049
+ const index = options.indexOf(firstEnabled);
2242
2050
  this.activeOptionId.set(firstEnabled.id);
2051
+ this.scrollActiveOptionIntoView(index, firstEnabled.id);
2243
2052
  }
2244
2053
  }
2245
2054
  navigateToLast() {
2246
2055
  const options = this.groupBy() ? this.flattenGroupedOptions() : this.filteredOptions();
2247
2056
  const lastEnabled = [...options].reverse().find((opt) => !opt.disabled);
2248
2057
  if (lastEnabled) {
2058
+ const index = options.indexOf(lastEnabled);
2249
2059
  this.activeOptionId.set(lastEnabled.id);
2060
+ this.scrollActiveOptionIntoView(index, lastEnabled.id);
2061
+ }
2062
+ }
2063
+ /** Scrolls the active option into view, using virtual viewport or native scroll. */
2064
+ scrollActiveOptionIntoView(index, optionId) {
2065
+ const viewport = this.virtualViewport();
2066
+ if (viewport) {
2067
+ viewport.scrollToIndex(index);
2068
+ }
2069
+ else {
2070
+ const panelEl = this.overlayRef?.overlayElement;
2071
+ const optionEl = panelEl?.querySelector(`[id="${optionId}"]`);
2072
+ optionEl?.scrollIntoView?.({ block: 'nearest', inline: 'nearest' });
2250
2073
  }
2251
2074
  }
2252
2075
  selectActiveOption() {
@@ -2280,7 +2103,7 @@ class ComDropdown {
2280
2103
  this.liveAnnouncer.announce(message, 'polite');
2281
2104
  }
2282
2105
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: ComDropdown, deps: [], target: i0.ɵɵFactoryTarget.Component });
2283
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.0", type: ComDropdown, isStandalone: true, selector: "com-dropdown", inputs: { options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, searchable: { classPropertyName: "searchable", publicName: "searchable", isSignal: true, isRequired: false, transformFunction: null }, searchPlaceholder: { classPropertyName: "searchPlaceholder", publicName: "searchPlaceholder", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, clearable: { classPropertyName: "clearable", publicName: "clearable", isSignal: true, isRequired: false, transformFunction: null }, compareWith: { classPropertyName: "compareWith", publicName: "compareWith", isSignal: true, isRequired: false, transformFunction: null }, displayWith: { classPropertyName: "displayWith", publicName: "displayWith", isSignal: true, isRequired: false, transformFunction: null }, filterWith: { classPropertyName: "filterWith", publicName: "filterWith", isSignal: true, isRequired: false, transformFunction: null }, groupBy: { classPropertyName: "groupBy", publicName: "groupBy", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, state: { classPropertyName: "state", publicName: "state", isSignal: true, isRequired: false, transformFunction: null }, userClass: { classPropertyName: "userClass", publicName: "class", isSignal: true, isRequired: false, transformFunction: null }, panelClass: { classPropertyName: "panelClass", publicName: "panelClass", isSignal: true, isRequired: false, transformFunction: null }, maxHeight: { classPropertyName: "maxHeight", publicName: "maxHeight", isSignal: true, isRequired: false, transformFunction: null }, panelWidth: { classPropertyName: "panelWidth", publicName: "panelWidth", isSignal: true, isRequired: false, transformFunction: null }, searchDebounceMs: { classPropertyName: "searchDebounceMs", publicName: "searchDebounceMs", isSignal: true, isRequired: false, transformFunction: null }, virtualScrollThreshold: { classPropertyName: "virtualScrollThreshold", publicName: "virtualScrollThreshold", isSignal: true, isRequired: false, transformFunction: null }, maxVisibleTags: { classPropertyName: "maxVisibleTags", publicName: "maxVisibleTags", isSignal: true, isRequired: false, transformFunction: null }, errorStateMatcher: { classPropertyName: "errorStateMatcher", publicName: "errorStateMatcher", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, sfErrors: { classPropertyName: "sfErrors", publicName: "errors", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", touched: "touchedChange", searchChange: "searchChange", opened: "opened", closed: "closed" }, host: { properties: { "class.com-dropdown-disabled": "disabled()", "class.com-dropdown-open": "isOpen()" }, classAttribute: "com-dropdown-host inline-block" }, providers: [{ provide: FormFieldControl, useExisting: forwardRef(() => ComDropdown) }], queries: [{ propertyName: "optionTemplate", first: true, predicate: (ComDropdownOptionTpl), descendants: true, isSignal: true }, { propertyName: "selectedTemplate", first: true, predicate: (ComDropdownSelectedTpl), descendants: true, isSignal: true }, { propertyName: "emptyTemplate", first: true, predicate: ComDropdownEmptyTpl, descendants: true, isSignal: true }, { propertyName: "groupTemplate", first: true, predicate: ComDropdownGroupTpl, descendants: true, isSignal: true }, { propertyName: "tagTemplate", first: true, predicate: (ComDropdownTagTpl), descendants: true, isSignal: true }], viewQueries: [{ propertyName: "triggerRef", first: true, predicate: ["triggerElement"], descendants: true, isSignal: true }, { propertyName: "panelTemplateRef", first: true, predicate: ["panelTemplate"], descendants: true, isSignal: true }], exportAs: ["comDropdown"], ngImport: i0, template: `
2106
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.0", type: ComDropdown, isStandalone: true, selector: "com-dropdown", inputs: { options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, searchable: { classPropertyName: "searchable", publicName: "searchable", isSignal: true, isRequired: false, transformFunction: null }, searchPlaceholder: { classPropertyName: "searchPlaceholder", publicName: "searchPlaceholder", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, clearable: { classPropertyName: "clearable", publicName: "clearable", isSignal: true, isRequired: false, transformFunction: null }, compareWith: { classPropertyName: "compareWith", publicName: "compareWith", isSignal: true, isRequired: false, transformFunction: null }, displayWith: { classPropertyName: "displayWith", publicName: "displayWith", isSignal: true, isRequired: false, transformFunction: null }, filterWith: { classPropertyName: "filterWith", publicName: "filterWith", isSignal: true, isRequired: false, transformFunction: null }, groupBy: { classPropertyName: "groupBy", publicName: "groupBy", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, state: { classPropertyName: "state", publicName: "state", isSignal: true, isRequired: false, transformFunction: null }, userClass: { classPropertyName: "userClass", publicName: "class", isSignal: true, isRequired: false, transformFunction: null }, panelClass: { classPropertyName: "panelClass", publicName: "panelClass", isSignal: true, isRequired: false, transformFunction: null }, maxHeight: { classPropertyName: "maxHeight", publicName: "maxHeight", isSignal: true, isRequired: false, transformFunction: null }, panelWidth: { classPropertyName: "panelWidth", publicName: "panelWidth", isSignal: true, isRequired: false, transformFunction: null }, searchDebounceMs: { classPropertyName: "searchDebounceMs", publicName: "searchDebounceMs", isSignal: true, isRequired: false, transformFunction: null }, virtualScrollThreshold: { classPropertyName: "virtualScrollThreshold", publicName: "virtualScrollThreshold", isSignal: true, isRequired: false, transformFunction: null }, virtualScrollItemSize: { classPropertyName: "virtualScrollItemSize", publicName: "virtualScrollItemSize", isSignal: true, isRequired: false, transformFunction: null }, maxVisibleTags: { classPropertyName: "maxVisibleTags", publicName: "maxVisibleTags", isSignal: true, isRequired: false, transformFunction: null }, errorStateMatcher: { classPropertyName: "errorStateMatcher", publicName: "errorStateMatcher", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, sfErrors: { classPropertyName: "sfErrors", publicName: "errors", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", disabled: "disabledChange", touched: "touchedChange", searchChange: "searchChange", opened: "opened", closed: "closed" }, host: { properties: { "class.com-dropdown-disabled": "disabled()", "class.com-dropdown-open": "isOpen()" }, classAttribute: "com-dropdown-host inline-block" }, providers: [{ provide: FormFieldControl, useExisting: forwardRef(() => ComDropdown) }], queries: [{ propertyName: "optionTemplate", first: true, predicate: ComDropdownOptionTpl, descendants: true, isSignal: true }, { propertyName: "selectedTemplate", first: true, predicate: ComDropdownSelectedTpl, descendants: true, isSignal: true }, { propertyName: "emptyTemplate", first: true, predicate: ComDropdownEmptyTpl, descendants: true, isSignal: true }, { propertyName: "groupTemplate", first: true, predicate: ComDropdownGroupTpl, descendants: true, isSignal: true }, { propertyName: "tagTemplate", first: true, predicate: ComDropdownTagTpl, descendants: true, isSignal: true }], viewQueries: [{ propertyName: "triggerRef", first: true, predicate: ["triggerElement"], descendants: true, isSignal: true }, { propertyName: "panelTemplateRef", first: true, predicate: ["panelTemplate"], descendants: true, isSignal: true }, { propertyName: "virtualViewport", first: true, predicate: CdkVirtualScrollViewport, descendants: true, isSignal: true }], exportAs: ["comDropdown"], ngImport: i0, template: `
2284
2107
  <!-- Trigger button -->
2285
2108
  <button
2286
2109
  #triggerElement
@@ -2295,6 +2118,7 @@ class ComDropdown {
2295
2118
  [attr.aria-required]="required() || null"
2296
2119
  [attr.aria-invalid]="effectiveState() === 'error' || null"
2297
2120
  [attr.aria-describedby]="ariaDescribedBy()"
2121
+ [attr.aria-label]="hasValue() ? null : placeholder()"
2298
2122
  [attr.aria-disabled]="disabled() || null"
2299
2123
  [attr.tabindex]="disabled() ? -1 : 0"
2300
2124
  [disabled]="disabled()"
@@ -2386,6 +2210,7 @@ class ComDropdown {
2386
2210
  [class]="panelClasses()"
2387
2211
  [attr.id]="panelId()"
2388
2212
  [attr.role]="'listbox'"
2213
+ tabindex="-1"
2389
2214
  [attr.aria-multiselectable]="multiple() || null"
2390
2215
  [attr.aria-label]="placeholder()"
2391
2216
  (keydown)="onPanelKeydown($event)"
@@ -2402,28 +2227,71 @@ class ComDropdown {
2402
2227
  }
2403
2228
 
2404
2229
  <!-- Options list -->
2405
- <div
2406
- class="overflow-auto"
2407
- [style.maxHeight]="maxHeight()"
2408
- >
2409
- @if (groupedOptions().length > 0) {
2410
- @for (group of groupedOptions(); track group.key) {
2411
- <!-- Group header -->
2412
- <com-dropdown-group
2413
- [label]="group.key"
2414
- [count]="group.options.length"
2415
- [expanded]="group.expanded"
2416
- [size]="size()"
2417
- [groupTemplate]="groupTemplate()?.templateRef ?? null"
2418
- />
2230
+ @if (virtualScrollEnabled() && groupedOptions().length === 0 && filteredOptions().length > 0) {
2231
+ <!-- Virtual scrolling for large ungrouped lists -->
2232
+ <cdk-virtual-scroll-viewport
2233
+ [itemSize]="virtualScrollItemSize()"
2234
+ [maxBufferPx]="400"
2235
+ [minBufferPx]="200"
2236
+ [style.height]="maxHeight()"
2237
+ class="overflow-auto"
2238
+ >
2239
+ <com-dropdown-option
2240
+ *cdkVirtualFor="let option of filteredOptions(); trackBy: trackByOption; let i = index"
2241
+ [value]="option.value"
2242
+ [displayText]="option.displayText"
2243
+ [id]="option.id"
2244
+ [index]="i"
2245
+ [selected]="isSelected(option.value)"
2246
+ [active]="isActive(option.id)"
2247
+ [disabled]="option.disabled"
2248
+ [size]="size()"
2249
+ [optionTemplate]="optionTemplate()?.templateRef ?? null"
2250
+ (select)="selectOption($event)"
2251
+ (hover)="onOptionHover(option.id)"
2252
+ />
2253
+ </cdk-virtual-scroll-viewport>
2254
+ } @else {
2255
+ <!-- Standard rendering for small lists and grouped options -->
2256
+ <div
2257
+ class="overflow-auto"
2258
+ [style.maxHeight]="maxHeight()"
2259
+ >
2260
+ @if (groupedOptions().length > 0) {
2261
+ @for (group of groupedOptions(); track group.key) {
2262
+ <!-- Group header -->
2263
+ <com-dropdown-group
2264
+ [label]="group.key"
2265
+ [count]="group.options.length"
2266
+ [expanded]="group.expanded"
2267
+ [size]="size()"
2268
+ [groupTemplate]="groupTemplate()?.templateRef ?? null"
2269
+ />
2419
2270
 
2420
- <!-- Group options -->
2421
- @for (option of group.options; track option.id; let i = $index) {
2271
+ <!-- Group options -->
2272
+ @for (option of group.options; track option.id; let i = $index) {
2273
+ <com-dropdown-option
2274
+ [value]="option.value"
2275
+ [displayText]="option.displayText"
2276
+ [id]="option.id"
2277
+ [index]="getGlobalIndex(group.key, i)"
2278
+ [selected]="isSelected(option.value)"
2279
+ [active]="isActive(option.id)"
2280
+ [disabled]="option.disabled"
2281
+ [size]="size()"
2282
+ [optionTemplate]="optionTemplate()?.templateRef ?? null"
2283
+ (select)="selectOption($event)"
2284
+ (hover)="onOptionHover(option.id)"
2285
+ />
2286
+ }
2287
+ }
2288
+ } @else if (filteredOptions().length > 0) {
2289
+ @for (option of filteredOptions(); track option.id; let i = $index) {
2422
2290
  <com-dropdown-option
2423
2291
  [value]="option.value"
2424
2292
  [displayText]="option.displayText"
2425
2293
  [id]="option.id"
2426
- [index]="getGlobalIndex(group.key, i)"
2294
+ [index]="i"
2427
2295
  [selected]="isSelected(option.value)"
2428
2296
  [active]="isActive(option.id)"
2429
2297
  [disabled]="option.disabled"
@@ -2433,41 +2301,25 @@ class ComDropdown {
2433
2301
  (hover)="onOptionHover(option.id)"
2434
2302
  />
2435
2303
  }
2436
- }
2437
- } @else if (filteredOptions().length > 0) {
2438
- @for (option of filteredOptions(); track option.id; let i = $index) {
2439
- <com-dropdown-option
2440
- [value]="option.value"
2441
- [displayText]="option.displayText"
2442
- [id]="option.id"
2443
- [index]="i"
2444
- [selected]="isSelected(option.value)"
2445
- [active]="isActive(option.id)"
2446
- [disabled]="option.disabled"
2447
- [size]="size()"
2448
- [optionTemplate]="optionTemplate()?.templateRef ?? null"
2449
- (select)="selectOption($event)"
2450
- (hover)="onOptionHover(option.id)"
2451
- />
2452
- }
2453
- } @else {
2454
- <!-- Empty state -->
2455
- @if (emptyTemplate()) {
2456
- <ng-container
2457
- [ngTemplateOutlet]="emptyTemplate()!.templateRef"
2458
- [ngTemplateOutletContext]="emptyContext()"
2459
- />
2460
2304
  } @else {
2461
- <div class="flex items-center justify-center px-3 py-6 text-muted-foreground">
2462
- @if (searchQuery()) {
2463
- No results for "{{ searchQuery() }}"
2464
- } @else {
2465
- No options available
2466
- }
2467
- </div>
2305
+ <!-- Empty state -->
2306
+ @if (emptyTemplate()) {
2307
+ <ng-container
2308
+ [ngTemplateOutlet]="emptyTemplate()!.templateRef"
2309
+ [ngTemplateOutletContext]="emptyContext()"
2310
+ />
2311
+ } @else {
2312
+ <div class="flex items-center justify-center px-3 py-6 text-muted-foreground">
2313
+ @if (searchQuery()) {
2314
+ No results for "{{ searchQuery() }}"
2315
+ } @else {
2316
+ No options available
2317
+ }
2318
+ </div>
2319
+ }
2468
2320
  }
2469
- }
2470
- </div>
2321
+ </div>
2322
+ }
2471
2323
  </div>
2472
2324
  </ng-template>
2473
2325
 
@@ -2475,7 +2327,7 @@ class ComDropdown {
2475
2327
  <div class="sr-only" aria-live="polite" aria-atomic="true">
2476
2328
  {{ liveAnnouncement() }}
2477
2329
  </div>
2478
- `, isInline: true, styles: [".sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: OverlayModule }, { kind: "component", type: ComDropdownOption, selector: "com-dropdown-option", inputs: ["value", "displayText", "id", "index", "selected", "active", "disabled", "size", "optionTemplate", "class"], outputs: ["select", "hover"], exportAs: ["comDropdownOption"] }, { kind: "component", type: ComDropdownSearch, selector: "com-dropdown-search", inputs: ["placeholder", "ariaLabel", "disabled", "debounceMs", "size", "class"], outputs: ["searchChange", "keyNav"], exportAs: ["comDropdownSearch"] }, { kind: "component", type: ComDropdownTag, selector: "com-dropdown-tag", inputs: ["value", "displayText", "index", "disabled", "size", "variant", "class", "tagTemplate"], outputs: ["remove"], exportAs: ["comDropdownTag"] }, { kind: "component", type: ComDropdownGroup, selector: "com-dropdown-group", inputs: ["label", "count", "expanded", "showCount", "size", "class", "groupTemplate"], exportAs: ["comDropdownGroup"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2330
+ `, isInline: true, styles: [".sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: OverlayModule }, { kind: "directive", type: i1.ɵɵCdkFixedSizeVirtualScroll, selector: "cdk-virtual-scroll-viewport[itemSize]", inputs: ["itemSize", "minBufferPx", "maxBufferPx"] }, { kind: "directive", type: i1.ɵɵCdkVirtualForOf, selector: "[cdkVirtualFor][cdkVirtualForOf]", inputs: ["cdkVirtualForOf", "cdkVirtualForTrackBy", "cdkVirtualForTemplate", "cdkVirtualForTemplateCacheSize"] }, { kind: "component", type: i1.ɵɵCdkVirtualScrollViewport, selector: "cdk-virtual-scroll-viewport", inputs: ["orientation", "appendOnly"], outputs: ["scrolledIndexChange"] }, { kind: "component", type: ComDropdownOption, selector: "com-dropdown-option", inputs: ["value", "displayText", "id", "index", "selected", "active", "disabled", "size", "optionTemplate", "class"], outputs: ["select", "hover"], exportAs: ["comDropdownOption"] }, { kind: "component", type: ComDropdownSearch, selector: "com-dropdown-search", inputs: ["placeholder", "ariaLabel", "disabled", "debounceMs", "size", "class"], outputs: ["searchChange", "keyNav"], exportAs: ["comDropdownSearch"] }, { kind: "component", type: ComDropdownTag, selector: "com-dropdown-tag", inputs: ["value", "displayText", "index", "disabled", "size", "variant", "class", "tagTemplate"], outputs: ["remove"], exportAs: ["comDropdownTag"] }, { kind: "component", type: ComDropdownGroup, selector: "com-dropdown-group", inputs: ["label", "count", "expanded", "showCount", "size", "class", "groupTemplate"], exportAs: ["comDropdownGroup"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2479
2331
  }
2480
2332
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: ComDropdown, decorators: [{
2481
2333
  type: Component,
@@ -2494,6 +2346,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
2494
2346
  [attr.aria-required]="required() || null"
2495
2347
  [attr.aria-invalid]="effectiveState() === 'error' || null"
2496
2348
  [attr.aria-describedby]="ariaDescribedBy()"
2349
+ [attr.aria-label]="hasValue() ? null : placeholder()"
2497
2350
  [attr.aria-disabled]="disabled() || null"
2498
2351
  [attr.tabindex]="disabled() ? -1 : 0"
2499
2352
  [disabled]="disabled()"
@@ -2585,6 +2438,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
2585
2438
  [class]="panelClasses()"
2586
2439
  [attr.id]="panelId()"
2587
2440
  [attr.role]="'listbox'"
2441
+ tabindex="-1"
2588
2442
  [attr.aria-multiselectable]="multiple() || null"
2589
2443
  [attr.aria-label]="placeholder()"
2590
2444
  (keydown)="onPanelKeydown($event)"
@@ -2601,28 +2455,71 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
2601
2455
  }
2602
2456
 
2603
2457
  <!-- Options list -->
2604
- <div
2605
- class="overflow-auto"
2606
- [style.maxHeight]="maxHeight()"
2607
- >
2608
- @if (groupedOptions().length > 0) {
2609
- @for (group of groupedOptions(); track group.key) {
2610
- <!-- Group header -->
2611
- <com-dropdown-group
2612
- [label]="group.key"
2613
- [count]="group.options.length"
2614
- [expanded]="group.expanded"
2615
- [size]="size()"
2616
- [groupTemplate]="groupTemplate()?.templateRef ?? null"
2617
- />
2458
+ @if (virtualScrollEnabled() && groupedOptions().length === 0 && filteredOptions().length > 0) {
2459
+ <!-- Virtual scrolling for large ungrouped lists -->
2460
+ <cdk-virtual-scroll-viewport
2461
+ [itemSize]="virtualScrollItemSize()"
2462
+ [maxBufferPx]="400"
2463
+ [minBufferPx]="200"
2464
+ [style.height]="maxHeight()"
2465
+ class="overflow-auto"
2466
+ >
2467
+ <com-dropdown-option
2468
+ *cdkVirtualFor="let option of filteredOptions(); trackBy: trackByOption; let i = index"
2469
+ [value]="option.value"
2470
+ [displayText]="option.displayText"
2471
+ [id]="option.id"
2472
+ [index]="i"
2473
+ [selected]="isSelected(option.value)"
2474
+ [active]="isActive(option.id)"
2475
+ [disabled]="option.disabled"
2476
+ [size]="size()"
2477
+ [optionTemplate]="optionTemplate()?.templateRef ?? null"
2478
+ (select)="selectOption($event)"
2479
+ (hover)="onOptionHover(option.id)"
2480
+ />
2481
+ </cdk-virtual-scroll-viewport>
2482
+ } @else {
2483
+ <!-- Standard rendering for small lists and grouped options -->
2484
+ <div
2485
+ class="overflow-auto"
2486
+ [style.maxHeight]="maxHeight()"
2487
+ >
2488
+ @if (groupedOptions().length > 0) {
2489
+ @for (group of groupedOptions(); track group.key) {
2490
+ <!-- Group header -->
2491
+ <com-dropdown-group
2492
+ [label]="group.key"
2493
+ [count]="group.options.length"
2494
+ [expanded]="group.expanded"
2495
+ [size]="size()"
2496
+ [groupTemplate]="groupTemplate()?.templateRef ?? null"
2497
+ />
2618
2498
 
2619
- <!-- Group options -->
2620
- @for (option of group.options; track option.id; let i = $index) {
2499
+ <!-- Group options -->
2500
+ @for (option of group.options; track option.id; let i = $index) {
2501
+ <com-dropdown-option
2502
+ [value]="option.value"
2503
+ [displayText]="option.displayText"
2504
+ [id]="option.id"
2505
+ [index]="getGlobalIndex(group.key, i)"
2506
+ [selected]="isSelected(option.value)"
2507
+ [active]="isActive(option.id)"
2508
+ [disabled]="option.disabled"
2509
+ [size]="size()"
2510
+ [optionTemplate]="optionTemplate()?.templateRef ?? null"
2511
+ (select)="selectOption($event)"
2512
+ (hover)="onOptionHover(option.id)"
2513
+ />
2514
+ }
2515
+ }
2516
+ } @else if (filteredOptions().length > 0) {
2517
+ @for (option of filteredOptions(); track option.id; let i = $index) {
2621
2518
  <com-dropdown-option
2622
2519
  [value]="option.value"
2623
2520
  [displayText]="option.displayText"
2624
2521
  [id]="option.id"
2625
- [index]="getGlobalIndex(group.key, i)"
2522
+ [index]="i"
2626
2523
  [selected]="isSelected(option.value)"
2627
2524
  [active]="isActive(option.id)"
2628
2525
  [disabled]="option.disabled"
@@ -2632,41 +2529,25 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
2632
2529
  (hover)="onOptionHover(option.id)"
2633
2530
  />
2634
2531
  }
2635
- }
2636
- } @else if (filteredOptions().length > 0) {
2637
- @for (option of filteredOptions(); track option.id; let i = $index) {
2638
- <com-dropdown-option
2639
- [value]="option.value"
2640
- [displayText]="option.displayText"
2641
- [id]="option.id"
2642
- [index]="i"
2643
- [selected]="isSelected(option.value)"
2644
- [active]="isActive(option.id)"
2645
- [disabled]="option.disabled"
2646
- [size]="size()"
2647
- [optionTemplate]="optionTemplate()?.templateRef ?? null"
2648
- (select)="selectOption($event)"
2649
- (hover)="onOptionHover(option.id)"
2650
- />
2651
- }
2652
- } @else {
2653
- <!-- Empty state -->
2654
- @if (emptyTemplate()) {
2655
- <ng-container
2656
- [ngTemplateOutlet]="emptyTemplate()!.templateRef"
2657
- [ngTemplateOutletContext]="emptyContext()"
2658
- />
2659
2532
  } @else {
2660
- <div class="flex items-center justify-center px-3 py-6 text-muted-foreground">
2661
- @if (searchQuery()) {
2662
- No results for "{{ searchQuery() }}"
2663
- } @else {
2664
- No options available
2665
- }
2666
- </div>
2533
+ <!-- Empty state -->
2534
+ @if (emptyTemplate()) {
2535
+ <ng-container
2536
+ [ngTemplateOutlet]="emptyTemplate()!.templateRef"
2537
+ [ngTemplateOutletContext]="emptyContext()"
2538
+ />
2539
+ } @else {
2540
+ <div class="flex items-center justify-center px-3 py-6 text-muted-foreground">
2541
+ @if (searchQuery()) {
2542
+ No results for "{{ searchQuery() }}"
2543
+ } @else {
2544
+ No options available
2545
+ }
2546
+ </div>
2547
+ }
2667
2548
  }
2668
- }
2669
- </div>
2549
+ </div>
2550
+ }
2670
2551
  </div>
2671
2552
  </ng-template>
2672
2553
 
@@ -2677,6 +2558,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
2677
2558
  `, imports: [
2678
2559
  NgTemplateOutlet,
2679
2560
  OverlayModule,
2561
+ CdkVirtualScrollViewport,
2562
+ CdkFixedSizeVirtualScroll,
2563
+ CdkVirtualForOf,
2680
2564
  ComDropdownOption,
2681
2565
  ComDropdownSearch,
2682
2566
  ComDropdownTag,
@@ -2686,7 +2570,229 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
2686
2570
  '[class.com-dropdown-disabled]': 'disabled()',
2687
2571
  '[class.com-dropdown-open]': 'isOpen()',
2688
2572
  }, styles: [".sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}\n"] }]
2689
- }], ctorParameters: () => [], propDecorators: { triggerRef: [{ type: i0.ViewChild, args: ['triggerElement', { isSignal: true }] }], panelTemplateRef: [{ type: i0.ViewChild, args: ['panelTemplate', { isSignal: true }] }], optionTemplate: [{ type: i0.ContentChild, args: [i0.forwardRef(() => ComDropdownOptionTpl), { isSignal: true }] }], selectedTemplate: [{ type: i0.ContentChild, args: [i0.forwardRef(() => ComDropdownSelectedTpl), { isSignal: true }] }], emptyTemplate: [{ type: i0.ContentChild, args: [i0.forwardRef(() => ComDropdownEmptyTpl), { isSignal: true }] }], groupTemplate: [{ type: i0.ContentChild, args: [i0.forwardRef(() => ComDropdownGroupTpl), { isSignal: true }] }], tagTemplate: [{ type: i0.ContentChild, args: [i0.forwardRef(() => ComDropdownTagTpl), { isSignal: true }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], searchable: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchable", required: false }] }], searchPlaceholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchPlaceholder", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], clearable: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearable", required: false }] }], compareWith: [{ type: i0.Input, args: [{ isSignal: true, alias: "compareWith", required: false }] }], displayWith: [{ type: i0.Input, args: [{ isSignal: true, alias: "displayWith", required: false }] }], filterWith: [{ type: i0.Input, args: [{ isSignal: true, alias: "filterWith", required: false }] }], groupBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "groupBy", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], state: [{ type: i0.Input, args: [{ isSignal: true, alias: "state", required: false }] }], userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }], panelClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "panelClass", required: false }] }], maxHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxHeight", required: false }] }], panelWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "panelWidth", required: false }] }], searchDebounceMs: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchDebounceMs", required: false }] }], virtualScrollThreshold: [{ type: i0.Input, args: [{ isSignal: true, alias: "virtualScrollThreshold", required: false }] }], maxVisibleTags: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxVisibleTags", required: false }] }], errorStateMatcher: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorStateMatcher", required: false }] }], touched: [{ type: i0.Input, args: [{ isSignal: true, alias: "touched", required: false }] }, { type: i0.Output, args: ["touchedChange"] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], sfErrors: [{ type: i0.Input, args: [{ isSignal: true, alias: "errors", required: false }] }], searchChange: [{ type: i0.Output, args: ["searchChange"] }], opened: [{ type: i0.Output, args: ["opened"] }], closed: [{ type: i0.Output, args: ["closed"] }] } });
2573
+ }], ctorParameters: () => [], propDecorators: { triggerRef: [{ type: i0.ViewChild, args: ['triggerElement', { isSignal: true }] }], panelTemplateRef: [{ type: i0.ViewChild, args: ['panelTemplate', { isSignal: true }] }], virtualViewport: [{ type: i0.ViewChild, args: [i0.forwardRef(() => CdkVirtualScrollViewport), { isSignal: true }] }], optionTemplate: [{ type: i0.ContentChild, args: [i0.forwardRef(() => ComDropdownOptionTpl), { isSignal: true }] }], selectedTemplate: [{ type: i0.ContentChild, args: [i0.forwardRef(() => ComDropdownSelectedTpl), { isSignal: true }] }], emptyTemplate: [{ type: i0.ContentChild, args: [i0.forwardRef(() => ComDropdownEmptyTpl), { isSignal: true }] }], groupTemplate: [{ type: i0.ContentChild, args: [i0.forwardRef(() => ComDropdownGroupTpl), { isSignal: true }] }], tagTemplate: [{ type: i0.ContentChild, args: [i0.forwardRef(() => ComDropdownTagTpl), { isSignal: true }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], searchable: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchable", required: false }] }], searchPlaceholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchPlaceholder", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }, { type: i0.Output, args: ["disabledChange"] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], clearable: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearable", required: false }] }], compareWith: [{ type: i0.Input, args: [{ isSignal: true, alias: "compareWith", required: false }] }], displayWith: [{ type: i0.Input, args: [{ isSignal: true, alias: "displayWith", required: false }] }], filterWith: [{ type: i0.Input, args: [{ isSignal: true, alias: "filterWith", required: false }] }], groupBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "groupBy", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], state: [{ type: i0.Input, args: [{ isSignal: true, alias: "state", required: false }] }], userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }], panelClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "panelClass", required: false }] }], maxHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxHeight", required: false }] }], panelWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "panelWidth", required: false }] }], searchDebounceMs: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchDebounceMs", required: false }] }], virtualScrollThreshold: [{ type: i0.Input, args: [{ isSignal: true, alias: "virtualScrollThreshold", required: false }] }], virtualScrollItemSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "virtualScrollItemSize", required: false }] }], maxVisibleTags: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxVisibleTags", required: false }] }], errorStateMatcher: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorStateMatcher", required: false }] }], touched: [{ type: i0.Input, args: [{ isSignal: true, alias: "touched", required: false }] }, { type: i0.Output, args: ["touchedChange"] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], sfErrors: [{ type: i0.Input, args: [{ isSignal: true, alias: "errors", required: false }] }], searchChange: [{ type: i0.Output, args: ["searchChange"] }], opened: [{ type: i0.Output, args: ["opened"] }], closed: [{ type: i0.Output, args: ["closed"] }] } });
2574
+
2575
+ /**
2576
+ * The overlay panel containing the dropdown options.
2577
+ * Supports virtual scrolling for large lists.
2578
+ *
2579
+ * @example
2580
+ * ```html
2581
+ * <com-dropdown-panel
2582
+ * [options]="filteredOptions()"
2583
+ * [maxHeight]="'300px'"
2584
+ * [virtualScrollEnabled]="true"
2585
+ * >
2586
+ * <ng-content />
2587
+ * </com-dropdown-panel>
2588
+ * ```
2589
+ *
2590
+ * @tokens `--color-popover`, `--color-popover-foreground`, `--color-border-subtle`,
2591
+ * `--color-muted-foreground`, `--radius-overlay`
2592
+ */
2593
+ class ComDropdownPanel {
2594
+ /** Reference to the panel element. */
2595
+ panelRef = viewChild('panelElement', ...(ngDevMode ? [{ debugName: "panelRef" }] : []));
2596
+ /** Reference to the virtual scroll viewport (when enabled). */
2597
+ viewport = viewChild('viewport', ...(ngDevMode ? [{ debugName: "viewport" }] : []));
2598
+ /** Unique identifier for the panel. */
2599
+ panelId = input('', ...(ngDevMode ? [{ debugName: "panelId" }] : []));
2600
+ /** The processed options to display. */
2601
+ options = input([], ...(ngDevMode ? [{ debugName: "options" }] : []));
2602
+ /** Maximum height of the panel. */
2603
+ maxHeight = input('256px', ...(ngDevMode ? [{ debugName: "maxHeight" }] : []));
2604
+ /** Whether multiple selection is enabled. */
2605
+ multiselectable = input(false, ...(ngDevMode ? [{ debugName: "multiselectable" }] : []));
2606
+ /** Size variant for styling. */
2607
+ size = input('default', ...(ngDevMode ? [{ debugName: "size" }] : []));
2608
+ /** Additional CSS classes to apply to the panel. */
2609
+ panelClass = input('', ...(ngDevMode ? [{ debugName: "panelClass" }] : []));
2610
+ /** Whether virtual scrolling is enabled. */
2611
+ virtualScrollEnabled = input(false, ...(ngDevMode ? [{ debugName: "virtualScrollEnabled" }] : []));
2612
+ /** Item size for virtual scrolling (in pixels). */
2613
+ itemSize = input(40, ...(ngDevMode ? [{ debugName: "itemSize" }] : []));
2614
+ /** The current search query (for empty state context). */
2615
+ searchQuery = input('', ...(ngDevMode ? [{ debugName: "searchQuery" }] : []));
2616
+ /** Custom empty state text. */
2617
+ emptyText = input('No options available', ...(ngDevMode ? [{ debugName: "emptyText" }] : []));
2618
+ /** Template for rendering each option. */
2619
+ optionTemplate = input.required(...(ngDevMode ? [{ debugName: "optionTemplate" }] : []));
2620
+ /** Custom template for the empty state. */
2621
+ emptyTemplate = input(null, ...(ngDevMode ? [{ debugName: "emptyTemplate" }] : []));
2622
+ /** Emitted when the panel is scrolled. */
2623
+ scrolled = output();
2624
+ /** Whether to show the empty state. */
2625
+ showEmpty = computed(() => this.options().length === 0, ...(ngDevMode ? [{ debugName: "showEmpty" }] : []));
2626
+ /** Computed CSS classes for the panel. */
2627
+ panelClasses = computed(() => {
2628
+ const baseClasses = dropdownPanelVariants({ size: this.size() });
2629
+ return mergeClasses(baseClasses, this.panelClass());
2630
+ }, ...(ngDevMode ? [{ debugName: "panelClasses" }] : []));
2631
+ /** Computed CSS classes for the empty state. */
2632
+ emptyClasses = computed(() => {
2633
+ return dropdownEmptyVariants({ size: this.size() });
2634
+ }, ...(ngDevMode ? [{ debugName: "emptyClasses" }] : []));
2635
+ /** Template context for the empty state. */
2636
+ emptyContext = computed(() => ({
2637
+ $implicit: this.searchQuery(),
2638
+ }), ...(ngDevMode ? [{ debugName: "emptyContext" }] : []));
2639
+ /** Track function for options. */
2640
+ trackByFn(_index, option) {
2641
+ return option.id;
2642
+ }
2643
+ /** Scrolls to a specific index. */
2644
+ scrollToIndex(index) {
2645
+ const vp = this.viewport();
2646
+ if (this.virtualScrollEnabled() && vp) {
2647
+ vp.scrollToIndex(index);
2648
+ }
2649
+ else {
2650
+ const panelEl = this.panelRef()?.nativeElement;
2651
+ const optionEl = panelEl?.querySelector(`[data-index="${index}"]`);
2652
+ optionEl?.scrollIntoView({ block: 'nearest', inline: 'nearest' });
2653
+ }
2654
+ }
2655
+ /** Scrolls an option into view. */
2656
+ scrollOptionIntoView(optionElement) {
2657
+ optionElement.scrollIntoView({ block: 'nearest', inline: 'nearest' });
2658
+ }
2659
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: ComDropdownPanel, deps: [], target: i0.ɵɵFactoryTarget.Component });
2660
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.0", type: ComDropdownPanel, isStandalone: true, selector: "com-dropdown-panel", inputs: { panelId: { classPropertyName: "panelId", publicName: "panelId", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, maxHeight: { classPropertyName: "maxHeight", publicName: "maxHeight", isSignal: true, isRequired: false, transformFunction: null }, multiselectable: { classPropertyName: "multiselectable", publicName: "multiselectable", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, panelClass: { classPropertyName: "panelClass", publicName: "panelClass", isSignal: true, isRequired: false, transformFunction: null }, virtualScrollEnabled: { classPropertyName: "virtualScrollEnabled", publicName: "virtualScrollEnabled", isSignal: true, isRequired: false, transformFunction: null }, itemSize: { classPropertyName: "itemSize", publicName: "itemSize", isSignal: true, isRequired: false, transformFunction: null }, searchQuery: { classPropertyName: "searchQuery", publicName: "searchQuery", isSignal: true, isRequired: false, transformFunction: null }, emptyText: { classPropertyName: "emptyText", publicName: "emptyText", isSignal: true, isRequired: false, transformFunction: null }, optionTemplate: { classPropertyName: "optionTemplate", publicName: "optionTemplate", isSignal: true, isRequired: true, transformFunction: null }, emptyTemplate: { classPropertyName: "emptyTemplate", publicName: "emptyTemplate", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { scrolled: "scrolled" }, host: { classAttribute: "com-dropdown-panel-host block" }, viewQueries: [{ propertyName: "panelRef", first: true, predicate: ["panelElement"], descendants: true, isSignal: true }, { propertyName: "viewport", first: true, predicate: ["viewport"], descendants: true, isSignal: true }], exportAs: ["comDropdownPanel"], ngImport: i0, template: `
2661
+ <div
2662
+ #panelElement
2663
+ [class]="panelClasses()"
2664
+ [attr.role]="'listbox'"
2665
+ [attr.aria-multiselectable]="multiselectable() || null"
2666
+ [attr.id]="panelId()"
2667
+ >
2668
+ <!-- Search slot -->
2669
+ <ng-content select="[comDropdownSearch]" />
2670
+
2671
+ <!-- Options container -->
2672
+ @if (virtualScrollEnabled()) {
2673
+ <cdk-virtual-scroll-viewport
2674
+ [itemSize]="itemSize()"
2675
+ [maxBufferPx]="400"
2676
+ [minBufferPx]="200"
2677
+ [style.height]="maxHeight()"
2678
+ class="overflow-auto"
2679
+ >
2680
+ <div
2681
+ *cdkVirtualFor="let option of options(); trackBy: trackByFn; let i = index"
2682
+ [attr.data-index]="i"
2683
+ >
2684
+ <ng-container
2685
+ [ngTemplateOutlet]="optionTemplate()"
2686
+ [ngTemplateOutletContext]="{ $implicit: option, index: i }"
2687
+ />
2688
+ </div>
2689
+ </cdk-virtual-scroll-viewport>
2690
+ } @else {
2691
+ <div
2692
+ class="overflow-auto"
2693
+ [style.maxHeight]="maxHeight()"
2694
+ >
2695
+ @for (option of options(); track option.id; let i = $index) {
2696
+ <ng-container
2697
+ [ngTemplateOutlet]="optionTemplate()"
2698
+ [ngTemplateOutletContext]="{ $implicit: option, index: i }"
2699
+ />
2700
+ }
2701
+ </div>
2702
+ }
2703
+
2704
+ <!-- Empty state -->
2705
+ @if (showEmpty()) {
2706
+ @if (emptyTemplate()) {
2707
+ <ng-container
2708
+ [ngTemplateOutlet]="emptyTemplate()!"
2709
+ [ngTemplateOutletContext]="emptyContext()"
2710
+ />
2711
+ } @else {
2712
+ <div [class]="emptyClasses()">
2713
+ {{ emptyText() }}
2714
+ </div>
2715
+ }
2716
+ }
2717
+ </div>
2718
+ `, isInline: true, dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: CdkVirtualScrollViewport, selector: "cdk-virtual-scroll-viewport", inputs: ["orientation", "appendOnly"], outputs: ["scrolledIndexChange"] }, { kind: "directive", type: CdkFixedSizeVirtualScroll, selector: "cdk-virtual-scroll-viewport[itemSize]", inputs: ["itemSize", "minBufferPx", "maxBufferPx"] }, { kind: "directive", type: CdkVirtualForOf, selector: "[cdkVirtualFor][cdkVirtualForOf]", inputs: ["cdkVirtualForOf", "cdkVirtualForTrackBy", "cdkVirtualForTemplate", "cdkVirtualForTemplateCacheSize"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2719
+ }
2720
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: ComDropdownPanel, decorators: [{
2721
+ type: Component,
2722
+ args: [{
2723
+ selector: 'com-dropdown-panel',
2724
+ exportAs: 'comDropdownPanel',
2725
+ template: `
2726
+ <div
2727
+ #panelElement
2728
+ [class]="panelClasses()"
2729
+ [attr.role]="'listbox'"
2730
+ [attr.aria-multiselectable]="multiselectable() || null"
2731
+ [attr.id]="panelId()"
2732
+ >
2733
+ <!-- Search slot -->
2734
+ <ng-content select="[comDropdownSearch]" />
2735
+
2736
+ <!-- Options container -->
2737
+ @if (virtualScrollEnabled()) {
2738
+ <cdk-virtual-scroll-viewport
2739
+ [itemSize]="itemSize()"
2740
+ [maxBufferPx]="400"
2741
+ [minBufferPx]="200"
2742
+ [style.height]="maxHeight()"
2743
+ class="overflow-auto"
2744
+ >
2745
+ <div
2746
+ *cdkVirtualFor="let option of options(); trackBy: trackByFn; let i = index"
2747
+ [attr.data-index]="i"
2748
+ >
2749
+ <ng-container
2750
+ [ngTemplateOutlet]="optionTemplate()"
2751
+ [ngTemplateOutletContext]="{ $implicit: option, index: i }"
2752
+ />
2753
+ </div>
2754
+ </cdk-virtual-scroll-viewport>
2755
+ } @else {
2756
+ <div
2757
+ class="overflow-auto"
2758
+ [style.maxHeight]="maxHeight()"
2759
+ >
2760
+ @for (option of options(); track option.id; let i = $index) {
2761
+ <ng-container
2762
+ [ngTemplateOutlet]="optionTemplate()"
2763
+ [ngTemplateOutletContext]="{ $implicit: option, index: i }"
2764
+ />
2765
+ }
2766
+ </div>
2767
+ }
2768
+
2769
+ <!-- Empty state -->
2770
+ @if (showEmpty()) {
2771
+ @if (emptyTemplate()) {
2772
+ <ng-container
2773
+ [ngTemplateOutlet]="emptyTemplate()!"
2774
+ [ngTemplateOutletContext]="emptyContext()"
2775
+ />
2776
+ } @else {
2777
+ <div [class]="emptyClasses()">
2778
+ {{ emptyText() }}
2779
+ </div>
2780
+ }
2781
+ }
2782
+ </div>
2783
+ `,
2784
+ imports: [
2785
+ NgTemplateOutlet,
2786
+ CdkVirtualScrollViewport,
2787
+ CdkFixedSizeVirtualScroll,
2788
+ CdkVirtualForOf,
2789
+ ],
2790
+ changeDetection: ChangeDetectionStrategy.OnPush,
2791
+ host: {
2792
+ class: 'com-dropdown-panel-host block',
2793
+ },
2794
+ }]
2795
+ }], propDecorators: { panelRef: [{ type: i0.ViewChild, args: ['panelElement', { isSignal: true }] }], viewport: [{ type: i0.ViewChild, args: ['viewport', { isSignal: true }] }], panelId: [{ type: i0.Input, args: [{ isSignal: true, alias: "panelId", required: false }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], maxHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxHeight", required: false }] }], multiselectable: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiselectable", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], panelClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "panelClass", required: false }] }], virtualScrollEnabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "virtualScrollEnabled", required: false }] }], itemSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "itemSize", required: false }] }], searchQuery: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchQuery", required: false }] }], emptyText: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyText", required: false }] }], optionTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "optionTemplate", required: true }] }], emptyTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyTemplate", required: false }] }], scrolled: [{ type: i0.Output, args: ["scrolled"] }] } });
2690
2796
 
2691
2797
  // Public API for the dropdown component
2692
2798
  // Main component