UNPKG

kaspacom-ui

Version:

UI Component Library for KaspaCom DeFi Applications

1 lines 261 kB
{"version":3,"file":"kaspacom-ui.mjs","sources":["../../../projects/kaspacom-ui/src/lib/icons/icon/icon.component.ts","../../../projects/kaspacom-ui/src/lib/icons/icon/icon.component.html","../../../projects/kaspacom-ui/src/lib/modals/kc-base-modal/kc-base-modal.component.ts","../../../projects/kaspacom-ui/src/lib/modals/kc-base-modal/kc-base-modal.component.html","../../../projects/kaspacom-ui/src/lib/loading/spinner/spinner.component.ts","../../../projects/kaspacom-ui/src/lib/form-controls/components/button/button.component.ts","../../../projects/kaspacom-ui/src/lib/form-controls/components/checkbox/checkbox.component.ts","../../../projects/kaspacom-ui/src/lib/form-controls/components/dropdown-select/dropdown-options/dropdown-options.component.ts","../../../projects/kaspacom-ui/src/lib/form-controls/components/dropdown-select/dropdown-options/dropdown-options.component.html","../../../projects/kaspacom-ui/src/lib/form-controls/services/responsive.service.ts","../../../projects/kaspacom-ui/src/lib/form-controls/components/dropdown-select/dropdown-select.component.ts","../../../projects/kaspacom-ui/src/lib/form-controls/components/dropdown-select/dropdown-select.component.html","../../../projects/kaspacom-ui/src/lib/form-controls/components/dropdown-multiselect/dropdown-multiselect-options/dropdown-multiselect-options.component.ts","../../../projects/kaspacom-ui/src/lib/form-controls/components/dropdown-multiselect/dropdown-multiselect-options/dropdown-multiselect-options.component.html","../../../projects/kaspacom-ui/src/lib/form-controls/components/dropdown-multiselect/dropdown-multiselect.component.ts","../../../projects/kaspacom-ui/src/lib/form-controls/components/dropdown-multiselect/dropdown-multiselect.component.html","../../../projects/kaspacom-ui/src/lib/form-controls/components/chip/chip.component.ts","../../../projects/kaspacom-ui/src/lib/form-controls/components/switch-navigation/switch-navigation.component.ts","../../../projects/kaspacom-ui/src/lib/form-controls/components/switch-navigation/switch-navigation.component.html","../../../projects/kaspacom-ui/src/lib/form-controls/kc-input/kc-input.component.ts","../../../projects/kaspacom-ui/src/lib/form-controls/kc-input/kc-input.component.html","../../../projects/kaspacom-ui/src/lib/card/kc-card.component.ts","../../../projects/kaspacom-ui/src/lib/card/kc-card.component.html","../../../projects/kaspacom-ui/src/lib/snackbar/snackbar.models.ts","../../../projects/kaspacom-ui/src/lib/snackbar/notification.service.ts","../../../projects/kaspacom-ui/src/lib/snackbar/kc-snackbar/kc-snackbar.component.ts","../../../projects/kaspacom-ui/src/lib/snackbar/kc-snackbar/kc-snackbar.component.html","../../../projects/kaspacom-ui/src/lib/directives/tooltip/tooltip.directive.ts","../../../projects/kaspacom-ui/src/lib/showcase/design-system-showcase.component.ts","../../../projects/kaspacom-ui/src/lib/showcase/design-system-showcase.component.html","../../../projects/kaspacom-ui/src/public-api.ts","../../../projects/kaspacom-ui/src/kaspacom-ui.ts"],"sourcesContent":["import {\r\n Component,\r\n EventEmitter,\r\n HostListener,\r\n Inject,\r\n Input,\r\n OnInit,\r\n Output,\r\n} from '@angular/core';\r\nimport { DOCUMENT, NgClass, NgStyle } from '@angular/common';\r\nimport { ComponentSize } from '../../form-controls/types/sizing.type';\r\n\r\n@Component({\r\n // tslint:disable-next-line:component-selector\r\n selector: 'kc-icon',\r\n templateUrl: './icon.component.html',\r\n styleUrls: ['./icon.component.scss'],\r\n standalone: true,\r\n imports: [NgStyle, NgClass],\r\n})\r\nexport class KcIconComponent {\r\n @Input() iconClass!: string;\r\n @Input() public readonly disabled: boolean = false;\r\n @Input() size: ComponentSize = 'sm';\r\n @Input() public iconSize: ComponentSize = 'sm';\r\n @Input() public readonly classes?: string;\r\n @Input() public readonly isDefaultColor: boolean = true;\r\n @Input() public color?: string;\r\n\r\n constructor(@Inject(DOCUMENT) private _document: Document) {}\r\n}\r\n","<div\r\n [class]=\"'icon-wrapper ' + classes\"\r\n [ngStyle]=\"{color: color ? color : ''}\"\r\n [ngClass]=\"{\r\n disabled: disabled,\r\n xs: size === 'xs',\r\n sm: size === 'sm',\r\n md: size === 'md',\r\n lg: size === 'lg',\r\n xlg: size === 'xlg',\r\n 'default-color': isDefaultColor\r\n }\"\r\n>\r\n <div class=\"{{ iconClass }}\" [ngClass]=\"{icon: color ? false : true}\"></div>\r\n</div>\r\n","import { Component, ElementRef, input, output } from '@angular/core';\r\nimport { CommonModule } from '@angular/common';\r\nimport { ComponentSize } from '../../form-controls/types/sizing.type';\r\nimport { KcIconComponent } from '../../icons/icon/icon.component';\r\nimport { trigger, transition, style, animate } from '@angular/animations';\r\n\r\n@Component({\r\n selector: 'kc-base-modal',\r\n standalone: true,\r\n imports: [CommonModule, KcIconComponent],\r\n templateUrl: './kc-base-modal.component.html',\r\n styleUrls: ['./kc-base-modal.component.scss'],\r\n animations: [\r\n trigger('modalSlide', [\r\n transition(':enter', [\r\n style({ transform: 'translateY(100%)', opacity: 0 }),\r\n animate('350ms ease-out', style({ transform: 'translateY(0)', opacity: 1 }))\r\n ]),\r\n transition(':leave', [\r\n animate('300ms ease-in', style({ transform: 'translateY(100%)', opacity: 0 }))\r\n ])\r\n ]),\r\n trigger('overlayFade', [\r\n transition(':enter', [\r\n style({ opacity: 0 }),\r\n animate('250ms ease-out', style({ opacity: 1 }))\r\n ]),\r\n transition(':leave', [\r\n animate('200ms ease-in', style({ opacity: 0 }))\r\n ])\r\n ])\r\n ]\r\n})\r\nexport class KcBaseModalComponent {\r\n // Inputs\r\n showCloseButton = input<boolean>(true);\r\n title = input<string | undefined>(undefined);\r\n titleIconClass = input<string | undefined>(undefined);\r\n showHeaderSeperator = input<boolean>(true);\r\n autoWidth = input<boolean>(false);\r\n\r\n // Component size constants for the template\r\n componentSizes: Record<string, ComponentSize> = {\r\n XS: 'xs',\r\n SM: 'sm',\r\n MD: 'md',\r\n LG: 'lg',\r\n XLG: 'xlg'\r\n };\r\n \r\n // Outputs\r\n close = output<void>();\r\n\r\n constructor(private el: ElementRef) {}\r\n\r\n onClose(): void {\r\n this.el.nativeElement.classList.add('closing');\r\n\r\n setTimeout(() => {\r\n this.close.emit();\r\n }, 300);\r\n }\r\n}\r\n","<div class=\"modal-overlay\">\n <div class=\"modal-container\" [class.auto-width]=\"autoWidth()\">\n <div class=\"modal-header\" [class.header-separator]=\"showHeaderSeperator()\">\n <div class=\"modal-title\">\n <span *ngIf=\"titleIconClass()\" class=\"modal-title-icon\">\n <kc-icon [iconClass]=\"titleIconClass()!\" [size]=\"componentSizes['MD']\"></kc-icon>\n </span>\n <span *ngIf=\"title()\" class=\"modal-title-text typo-title-3\">{{ title() }}</span>\n </div>\n <div class=\"modal-header-controls\">\n <ng-content select=\"[rightSideSlot]\"></ng-content>\n <button *ngIf=\"showCloseButton()\" class=\"close-button\" (click)=\"onClose()\">\n <kc-icon iconClass=\"icon-close\"></kc-icon>\n </button>\n </div>\n </div>\n <div class=\"modal-content typo-text-3\">\n <ng-content></ng-content>\n </div>\n </div>\n</div> ","import { Component, input } from '@angular/core';\r\nimport { NgClass } from '@angular/common';\r\nimport { ComponentSize } from '../../form-controls/types/sizing.type';\r\n\r\n@Component({\r\n selector: 'kc-spinner',\r\n standalone: true,\r\n imports: [NgClass],\r\n template: `\r\n <div class=\"spinner\" [ngClass]=\"size()\"></div>\r\n `,\r\n styleUrls: ['./spinner.component.scss']\r\n})\r\nexport class KcSpinnerComponent {\r\n size = input<ComponentSize>('md');\r\n} ","import { Component, input, output, EventEmitter, HostBinding, ContentChild, TemplateRef } from '@angular/core';\r\nimport { NgClass, NgTemplateOutlet } from '@angular/common';\r\nimport { ComponentSize } from '../../types/sizing.type';\r\nimport { KcSpinnerComponent } from '../../../loading/spinner/spinner.component';\r\nimport { KcIconComponent } from '../../../icons/icon/icon.component';\r\nimport { ButtonVariant } from '../../types/button-variant.type';\r\n\r\n@Component({\r\n selector: 'kc-button',\r\n standalone: true,\r\n imports: [NgClass, NgTemplateOutlet, KcSpinnerComponent, KcIconComponent],\r\n template: `\r\n <button\r\n class=\"app-button\"\r\n [ngClass]=\"[\r\n variant(),\r\n size(),\r\n isFullWidth() ? 'full-width' : '',\r\n isDisabled() || isLoading() ? 'disabled' : '',\r\n isLoading() ? 'loading' : '',\r\n role() ? 'role-' + role() : '',\r\n getTypographyClass()\r\n ]\"\r\n [disabled]=\"isDisabled() || isLoading()\"\r\n (click)=\"handleClick($event)\"\r\n >\r\n <div class=\"button-content\">\r\n @if (isLoading()) {\r\n <div class=\"loading-content\">\r\n <kc-spinner [size]=\"getSpinnerSize()\"></kc-spinner>\r\n @if (loadingText()) {\r\n <span>{{ loadingText() }}</span>\r\n }\r\n </div>\r\n } @else {\r\n <div class=\"button-inner-content\">\r\n @if (prefixIcon()) {\r\n <kc-icon [iconClass]=\"prefixIcon()!\" [color]=\"prefixIconColor()\"></kc-icon>\r\n }\r\n @if (prefixTemplateRef) {\r\n <ng-container [ngTemplateOutlet]=\"prefixTemplateRef\"></ng-container>\r\n }\r\n @if (text()) {\r\n <span>{{ text() }}</span>\r\n }\r\n @if (suffixIcon()) {\r\n <kc-icon [iconClass]=\"suffixIcon()!\" [color]=\"suffixIconColor()\"></kc-icon>\r\n }\r\n </div>\r\n }\r\n </div>\r\n </button>\r\n `,\r\n styleUrls: ['./button.component.scss'],\r\n host: {\r\n '[style.display]': \"isFullWidth() ? 'block' : 'inline-block'\",\r\n '[style.width]': \"isFullWidth() ? '100%' : 'auto'\"\r\n }\r\n})\r\nexport class KcButtonComponent {\r\n text = input<string>('');\r\n variant = input<ButtonVariant>('primary');\r\n size = input<ComponentSize>('md');\r\n isLoading = input<boolean>(false);\r\n isFullWidth = input<boolean>(false);\r\n isDisabled = input<boolean>(false);\r\n role = input<'success' | 'info' | 'warning' | 'danger' | 'neutral' | null>(null);\r\n prefixIcon = input<string | undefined>(undefined);\r\n suffixIcon = input<string | undefined>(undefined);\r\n prefixIconColor = input<string | undefined>(undefined);\r\n suffixIconColor = input<string | undefined>(undefined);\r\n loadingText = input<string | undefined>(undefined);\r\n \r\n @ContentChild('prefixTemplate', { static: false }) prefixTemplateRef?: TemplateRef<any>;\r\n \r\n buttonClick = output<MouseEvent>();\r\n \r\n handleClick(event: MouseEvent): void {\r\n if (!this.isDisabled() && !this.isLoading()) {\r\n this.buttonClick.emit(event);\r\n }\r\n }\r\n \r\n getSpinnerSize(): ComponentSize {\r\n // Map button size to appropriate spinner size\r\n const sizeMap: Record<ComponentSize, ComponentSize> = {\r\n 'xs': 'xs',\r\n 'sm': 'xs',\r\n 'md': 'sm',\r\n 'lg': 'md',\r\n 'xlg': 'lg'\r\n };\r\n \r\n return sizeMap[this.size()];\r\n }\r\n \r\n getTypographyClass(): string {\r\n // Map button size to appropriate typography class\r\n const typographyMap: Record<ComponentSize, string> = {\r\n 'xs': 'typo-button-small',\r\n 'sm': 'typo-button-small',\r\n 'md': 'typo-button-medium',\r\n 'lg': 'typo-button-large',\r\n 'xlg': 'typo-button-large'\r\n };\r\n \r\n return typographyMap[this.size()];\r\n }\r\n} ","import { Component, forwardRef, input, Output, EventEmitter, OnInit, OnChanges, SimpleChanges } from '@angular/core';\r\nimport { CommonModule } from '@angular/common';\r\nimport { ControlValueAccessor, NG_VALUE_ACCESSOR, FormsModule } from '@angular/forms';\r\nimport { ComponentSize } from '../../types/sizing.type';\r\n\r\n@Component({\r\n selector: 'kc-checkbox',\r\n standalone: true,\r\n imports: [CommonModule, FormsModule],\r\n template: `\r\n <div \r\n class=\"checkbox-container\" \r\n [class.checked]=\"checked\"\r\n [class.disabled]=\"isDisabled()\"\r\n [ngClass]=\"sizeClass()\"\r\n (click)=\"toggle()\"\r\n >\r\n <div class=\"checkbox\">\r\n <div class=\"checkbox-inner\" [class.checked]=\"checked\">\r\n <svg *ngIf=\"checked\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"3\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\r\n <polyline points=\"20 6 9 17 4 12\"></polyline>\r\n </svg>\r\n </div>\r\n </div>\r\n <label *ngIf=\"label()\" class=\"checkbox-label\">{{ label() }}</label>\r\n </div>\r\n `,\r\n styles: [`\r\n .checkbox-container {\r\n display: inline-flex;\r\n align-items: center;\r\n gap: 8px;\r\n cursor: pointer;\r\n user-select: none;\r\n }\r\n \r\n .checkbox-container.disabled {\r\n opacity: 0.5;\r\n cursor: not-allowed;\r\n pointer-events: none;\r\n }\r\n \r\n .checkbox {\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n }\r\n \r\n .checkbox-inner {\r\n border-radius: 4px;\r\n border: 2px solid var(--gray-50);\r\n background-color: var(--gray-20);\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n transition: all 0.2s ease;\r\n box-sizing: border-box;\r\n position: relative;\r\n }\r\n \r\n .checkbox-inner.checked {\r\n border-color: var(--kaspa-50);\r\n background-color: var(--kaspa-50);\r\n }\r\n \r\n .checkbox-inner svg {\r\n color: white;\r\n position: absolute;\r\n top: 50%;\r\n left: 50%;\r\n transform: translate(-50%, -50%);\r\n width: 100%;\r\n height: 100%;\r\n }\r\n \r\n /* Sizing */\r\n .checkbox-container.xs .checkbox-inner {\r\n width: 14px;\r\n height: 14px;\r\n min-width: 14px;\r\n min-height: 14px;\r\n }\r\n \r\n .checkbox-container.sm .checkbox-inner {\r\n width: 16px;\r\n height: 16px;\r\n min-width: 16px;\r\n min-height: 16px;\r\n }\r\n \r\n .checkbox-container.md .checkbox-inner {\r\n width: 18px;\r\n height: 18px;\r\n min-width: 18px;\r\n min-height: 18px;\r\n }\r\n \r\n .checkbox-container.lg .checkbox-inner {\r\n width: 20px;\r\n height: 20px;\r\n min-width: 20px;\r\n min-height: 20px;\r\n }\r\n \r\n .checkbox-container.xlg .checkbox-inner {\r\n width: 24px;\r\n height: 24px;\r\n min-width: 24px;\r\n min-height: 24px;\r\n }\r\n \r\n .checkbox-container.xs svg {\r\n width: 10px !important;\r\n height: 10px !important;\r\n }\r\n \r\n .checkbox-container.sm svg {\r\n width: 12px !important;\r\n height: 12px !important;\r\n }\r\n \r\n .checkbox-container.md svg {\r\n width: 14px !important;\r\n height: 14px !important;\r\n }\r\n \r\n .checkbox-container.lg svg {\r\n width: 16px !important;\r\n height: 16px !important;\r\n }\r\n \r\n .checkbox-container.xlg svg {\r\n width: 18px !important;\r\n height: 18px !important;\r\n }\r\n \r\n /* Typography for labels */\r\n .checkbox-container.xs .checkbox-label {\r\n font-size: 0.75rem;\r\n }\r\n \r\n .checkbox-container.sm .checkbox-label {\r\n font-size: 0.875rem;\r\n }\r\n \r\n .checkbox-container.md .checkbox-label {\r\n font-size: 1rem;\r\n }\r\n \r\n .checkbox-container.lg .checkbox-label {\r\n font-size: 1.125rem;\r\n }\r\n \r\n .checkbox-container.xlg .checkbox-label {\r\n font-size: 1.25rem;\r\n }\r\n `],\r\n providers: [\r\n {\r\n provide: NG_VALUE_ACCESSOR,\r\n useExisting: forwardRef(() => KcCheckboxComponent),\r\n multi: true\r\n }\r\n ]\r\n})\r\nexport class KcCheckboxComponent implements ControlValueAccessor, OnInit, OnChanges {\r\n // Inputs\r\n size = input<ComponentSize>('md');\r\n isDisabled = input<boolean>(false);\r\n label = input<string>('');\r\n isChecked = input<boolean>(false);\r\n \r\n // State\r\n checked = false;\r\n \r\n // Output\r\n @Output() checkedChange = new EventEmitter<boolean>();\r\n \r\n // ControlValueAccessor\r\n private onChange: (value: boolean) => void = () => {};\r\n private onTouched: () => void = () => {};\r\n \r\n ngOnInit() {\r\n // Initialize the checked state from the isChecked input\r\n this.checked = this.isChecked();\r\n }\r\n \r\n ngOnChanges(changes: SimpleChanges) {\r\n // Update checked state when isChecked input changes\r\n if (changes['isChecked']) {\r\n this.checked = this.isChecked();\r\n }\r\n }\r\n \r\n // Calculate size class\r\n sizeClass() {\r\n // With string-based types, we can just return the size directly\r\n return this.size();\r\n }\r\n \r\n // Toggle the checkbox\r\n toggle() {\r\n if (this.isDisabled()) {\r\n return;\r\n }\r\n \r\n this.onTouched();\r\n this.checked = !this.checked;\r\n \r\n // Update the input value\r\n // Emit event for ngModel binding\r\n this.checkedChange.emit(this.checked);\r\n \r\n // Emit event for reactive forms\r\n this.onChange(this.checked);\r\n }\r\n \r\n // ControlValueAccessor implementation\r\n writeValue(value: boolean): void {\r\n this.checked = !!value;\r\n }\r\n \r\n registerOnChange(fn: (value: boolean) => void): void {\r\n this.onChange = fn;\r\n }\r\n \r\n registerOnTouched(fn: () => void): void {\r\n this.onTouched = fn;\r\n }\r\n \r\n setDisabledState(isDisabled: boolean): void {\r\n // Update the disabled state\r\n }\r\n} ","import { Component, EventEmitter, TemplateRef, Output, OnInit, OnDestroy, Input, ContentChild } from '@angular/core';\r\nimport { CommonModule, NgClass } from '@angular/common';\r\nimport { DropdownOption } from '../dropdown-select.models';\r\nimport { DropdownVariant } from '../../../types/dropdown-variant.type';\r\nimport { FormControl, ReactiveFormsModule } from '@angular/forms';\r\nimport { Subject, debounceTime, takeUntil } from 'rxjs';\r\n\r\n@Component({\r\n selector: 'kc-dropdown-options',\r\n standalone: true,\r\n imports: [CommonModule, NgClass, ReactiveFormsModule],\r\n templateUrl: './dropdown-options.component.html',\r\n styleUrls: ['./dropdown-options.component.scss']\r\n})\r\nexport class KcDropdownOptionsComponent implements OnInit, OnDestroy {\r\n options: DropdownOption[] = [];\r\n filteredOptions: DropdownOption[] = [];\r\n selectedValue: any = null;\r\n variant: DropdownVariant = 'secondary';\r\n \r\n @Input() optionsEllipsis: boolean = false;\r\n @Input() isFullscreenSelection: boolean = false;\r\n @Input() isSearchable: boolean = false;\r\n @Input() searchField: string = 'label';\r\n @Input() customTemplate: TemplateRef<any> | null = null;\r\n @ContentChild('emptyStateTemplate') emptyStateTemplate: TemplateRef<any> | null = null;\r\n \r\n searchControl = new FormControl('');\r\n private destroy$ = new Subject<void>();\r\n \r\n @Output() optionSelected = new EventEmitter<DropdownOption>();\r\n @Output() closeRequested = new EventEmitter<void>();\r\n\r\n ngOnInit(): void {\r\n this.filteredOptions = [...this.options];\r\n \r\n if (this.isSearchable) {\r\n this.searchControl.valueChanges.pipe(\r\n debounceTime(128),\r\n takeUntil(this.destroy$)\r\n ).subscribe(value => {\r\n this.filterOptions(value || '');\r\n });\r\n }\r\n }\r\n \r\n filterOptions(searchText: string): void {\r\n if (!searchText.trim()) {\r\n this.filteredOptions = [...this.options];\r\n return;\r\n }\r\n \r\n const searchLower = searchText.toLowerCase();\r\n this.filteredOptions = this.options.filter(option => {\r\n const field = this.searchField === 'label' ? option.label : \r\n (option as any)[this.searchField] || '';\r\n return String(field).toLowerCase().includes(searchLower);\r\n });\r\n }\r\n\r\n isSelected(option: DropdownOption): boolean {\r\n return this.selectedValue === option.value;\r\n }\r\n\r\n selectOption(option: DropdownOption): void {\r\n this.optionSelected.emit(option);\r\n }\r\n \r\n closeFullscreen(): void {\r\n this.closeRequested.emit();\r\n }\r\n \r\n getTypographyClass(): string {\r\n // Using the same typography class as the dropdown options\r\n return 'typo-text-3';\r\n }\r\n \r\n ngOnDestroy(): void {\r\n this.destroy$.next();\r\n this.destroy$.complete();\r\n }\r\n}\r\n","<div class=\"dropdown-options-container\" [ngClass]=\"[variant, isFullscreenSelection ? 'fullscreen-mode' : '']\">\r\n <!-- Fullscreen header with close button -->\r\n <div *ngIf=\"isFullscreenSelection\" class=\"fullscreen-header\">\r\n <div class=\"fullscreen-title typo-title-3\">Select an option</div>\r\n <button class=\"close-button\" (click)=\"closeFullscreen()\">\r\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\r\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"></line>\r\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"></line>\r\n </svg>\r\n </button>\r\n </div>\r\n \r\n <!-- Search input -->\r\n <div *ngIf=\"isSearchable\" class=\"search-container\">\r\n <div class=\"search-input-wrapper\">\r\n <svg class=\"search-icon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\r\n <circle cx=\"11\" cy=\"11\" r=\"8\"></circle>\r\n <line x1=\"21\" y1=\"21\" x2=\"16.65\" y2=\"16.65\"></line>\r\n </svg>\r\n <input \r\n type=\"text\" \r\n class=\"search-input\"\r\n [formControl]=\"searchControl\"\r\n placeholder=\"Search...\" \r\n [ngClass]=\"getTypographyClass()\"\r\n autocomplete=\"off\"\r\n />\r\n </div>\r\n </div>\r\n \r\n <div class=\"options-list\">\r\n <div\r\n *ngFor=\"let option of filteredOptions\"\r\n class=\"dropdown-option\"\r\n [class.disabled]=\"option.disabled\"\r\n [class.selected]=\"isSelected(option)\"\r\n [class.ellipsis]=\"optionsEllipsis\"\r\n [class.variant-primary]=\"variant === 'primary'\"\r\n [class.variant-secondary]=\"variant === 'secondary'\"\r\n [class.variant-tertiary]=\"variant === 'tertiary'\"\r\n [ngClass]=\"getTypographyClass()\"\r\n (click)=\"option.disabled ? null : selectOption(option)\">\r\n \r\n <!-- Use custom template when provided -->\r\n <ng-container *ngIf=\"customTemplate; else defaultTemplate\">\r\n <ng-container *ngTemplateOutlet=\"customTemplate; context: { $implicit: option }\"></ng-container>\r\n </ng-container>\r\n \r\n <!-- Default template -->\r\n <ng-template #defaultTemplate>\r\n {{ option.label }}\r\n </ng-template>\r\n </div>\r\n \r\n <!-- No results message -->\r\n <div *ngIf=\"filteredOptions.length === 0\" class=\"no-results\" [ngClass]=\"getTypographyClass()\">\r\n <!-- Use projected empty state template when provided -->\r\n <ng-content select=\"[emptyStateTemplate]\" *ngIf=\"emptyStateTemplate\"></ng-content>\r\n \r\n <!-- Default empty state template when no projection provided -->\r\n <ng-container *ngIf=\"!emptyStateTemplate\">\r\n <div class=\"typo-caption\">No matching options found</div>\r\n </ng-container>\r\n </div>\r\n </div>\r\n</div> ","import { Injectable, OnDestroy, inject, signal } from '@angular/core';\r\nimport { BreakpointObserver, Breakpoints } from '@angular/cdk/layout';\r\nimport { Subject, takeUntil } from 'rxjs';\r\n\r\n@Injectable({\r\n providedIn: 'root'\r\n})\r\nexport class ResponsiveService implements OnDestroy {\r\n private readonly destroy$ = new Subject<void>();\r\n private readonly breakpointObserver = inject(BreakpointObserver);\r\n \r\n // Signal that indicates if the screen is in mobile size (XS and below)\r\n isMobile = signal<boolean>(false);\r\n \r\n // Signal for screens <= 480px\r\n isExtraSmallScreen = signal<boolean>(false);\r\n \r\n constructor() {\r\n // Set the initial values based on the current screen size\r\n this.isMobile.set(this.breakpointObserver.isMatched(Breakpoints.XSmall));\r\n this.isExtraSmallScreen.set(this.breakpointObserver.isMatched('(max-width: 480px)'));\r\n \r\n // Observe changes to the breakpoints\r\n this.breakpointObserver\r\n .observe([Breakpoints.XSmall, '(max-width: 480px)'])\r\n .pipe(takeUntil(this.destroy$))\r\n .subscribe(result => {\r\n // Update the signals when the breakpoint states change\r\n this.isMobile.set(result.breakpoints[Breakpoints.XSmall]);\r\n this.isExtraSmallScreen.set(result.breakpoints['(max-width: 480px)']);\r\n });\r\n }\r\n \r\n // Also provide a method for custom breakpoint checking\r\n isBreakpointMatched(breakpoint: string): boolean {\r\n return this.breakpointObserver.isMatched(breakpoint);\r\n }\r\n \r\n ngOnDestroy(): void {\r\n this.destroy$.next();\r\n this.destroy$.complete();\r\n }\r\n} ","import {\n Component,\n ContentChild,\n effect,\n ElementRef,\n inject,\n input,\n OnDestroy,\n OnInit,\n output,\n TemplateRef,\n ViewChild\n} from '@angular/core';\nimport {CommonModule, NgClass} from '@angular/common';\nimport {Overlay, OverlayModule, OverlayRef} from '@angular/cdk/overlay';\nimport {ComponentPortal} from '@angular/cdk/portal';\nimport {ControlValueAccessor, FormsModule, NG_VALUE_ACCESSOR, ReactiveFormsModule} from '@angular/forms';\nimport {Subject, takeUntil} from 'rxjs';\n\nimport {KcDropdownOptionsComponent} from './dropdown-options/dropdown-options.component';\nimport {DropdownOption} from './dropdown-select.models';\nimport {DropdownVariant} from '../../types/dropdown-variant.type';\nimport {ComponentSize} from '../../types/sizing.type';\nimport {KcIconComponent} from '../../../icons/icon/icon.component';\nimport {ResponsiveService} from '../../services/responsive.service';\n\n@Component({\n selector: 'kc-dropdown-select',\n standalone: true,\n imports: [CommonModule, NgClass, FormsModule, ReactiveFormsModule, KcIconComponent, OverlayModule],\n templateUrl: './dropdown-select.component.html',\n styleUrls: ['./dropdown-select.component.scss'],\n providers: [\n {\n provide: NG_VALUE_ACCESSOR,\n useExisting: KcDropdownSelectComponent,\n multi: true\n }\n ]\n})\nexport class KcDropdownSelectComponent implements ControlValueAccessor, OnInit, OnDestroy {\n // Constants\n private readonly destroy$ = new Subject<void>();\n private readonly overlay = inject(Overlay);\n private readonly responsiveService = inject(ResponsiveService);\n\n // Signals\n options = input<DropdownOption[]>([]);\n placeholder = input<string>('Select an option');\n size = input<ComponentSize>('md');\n variant = input<DropdownVariant>('secondary');\n isFullWidth = input<boolean>(false);\n isDisabled = input<boolean>(false);\n optionsEllipsis = input<boolean>(false);\n isFullscreenSelection = input<boolean>(false);\n icon = input<string>('');\n useContentWidth = input<boolean>(true);\n maxWidth = input<string | null>(null);\n showToggleIcon = input<boolean>(true);\n\n // Value input for direct binding\n value = input<any>(null);\n\n // Search-related inputs\n isSearchable = input<boolean>(false);\n searchField = input<string>('label');\n\n // Custom item template\n @ContentChild('optionTemplate') optionTemplate: TemplateRef<any> | null = null;\n @ContentChild('prefixTemplate') prefixTemplateRef: TemplateRef<any> | null = null;\n @ContentChild('emptyStateTemplate') emptyStateTemplateRef: TemplateRef<any> | null = null;\n\n // Output signals\n valueChange = output<any>();\n\n // Other properties\n @ViewChild('dropdownTrigger') dropdownTrigger!: ElementRef;\n isOpen = false;\n private overlayRef: OverlayRef | null = null;\n private _value: any = null;\n private onChange: (value: any) => void = () => {};\n private onTouched: () => void = () => {};\n\n // We modify the isDisabled check to consider both the input signal and our internal state\n private _internalDisabled = false;\n\n constructor() {\n // Use an effect to respond to changes in the input value\n effect(() => {\n const newValue = this.value();\n if (this._value !== newValue) {\n this._value = newValue;\n }\n });\n }\n \n ngOnInit(): void {\n // We can't modify the input signal directly, so we'll create a computed property\n // that will check both the provided isFullscreenSelection and the screen size\n }\n\n // Helper method to check disabled state combining both sources\n isComponentDisabled(): boolean {\n return this.isDisabled() || this._internalDisabled;\n }\n\n // Get the display value for the dropdown\n getDisplayValue(): string {\n if (this._value === null || this._value === undefined) {\n return this.placeholder();\n }\n\n const selectedOption = this.options().find(option => option.value === this._value);\n return selectedOption ? selectedOption.label : this.placeholder();\n }\n\n // Map dropdown size to typography class\n getTypographyClass(): string {\n const typographyMap: Record<ComponentSize, string> = {\n 'xs': 'typo-text-1',\n 'sm': 'typo-text-2',\n 'md': 'typo-text-3',\n 'lg': 'typo-text-4',\n 'xlg': 'typo-text-5'\n };\n\n return typographyMap[this.size()];\n }\n\n // Methods\n toggleDropdown(): void {\n if (this.isComponentDisabled()) {\n return;\n }\n\n this.isOpen ? this.closeDropdown() : this.openDropdown();\n }\n\n openDropdown(): void {\n this.onTouched();\n\n if (this.overlayRef) {\n return;\n }\n\n // Check both the provided isFullscreenSelection and the screen size\n // If either is true, use fullscreen mode\n const shouldUseFullscreen = this.isFullscreenSelection() || this.responsiveService.isExtraSmallScreen();\n \n // Different overlay creation strategy based on mode\n if (shouldUseFullscreen) {\n this.openFullscreenDropdown();\n } else {\n this.openRegularDropdown();\n }\n\n this.isOpen = true;\n }\n\n private openRegularDropdown(): void {\n const positionStrategy = this.overlay\n .position()\n .flexibleConnectedTo(this.dropdownTrigger)\n .withPositions([\n {\n originX: 'start',\n originY: 'bottom',\n overlayX: 'start',\n overlayY: 'top',\n offsetY: 4\n },\n {\n originX: 'start',\n originY: 'top',\n overlayX: 'start',\n overlayY: 'bottom',\n offsetY: -4\n }\n ]);\n\n // Get parent width for minimum width constraint\n const parentWidth = this.dropdownTrigger.nativeElement.offsetWidth;\n \n // Configure overlay options based on the useContentWidth setting\n const overlayConfig: any = {\n positionStrategy,\n minWidth: parentWidth, // Always ensure minimum width matches parent\n scrollStrategy: this.overlay.scrollStrategies.close(),\n hasBackdrop: true,\n backdropClass: 'cdk-overlay-transparent-backdrop',\n panelClass: ['dropdown-overlay-panel', 'dropdown-animated-panel']\n };\n \n // Only set fixed width if useContentWidth is false\n if (!this.useContentWidth()) {\n overlayConfig.width = parentWidth;\n }\n \n // Apply maxWidth if provided\n if (this.maxWidth()) {\n overlayConfig.maxWidth = this.maxWidth();\n }\n \n this.overlayRef = this.overlay.create(overlayConfig);\n\n // Apply entrance animation\n const overlayElement = this.overlayRef.overlayElement;\n overlayElement.style.opacity = '0';\n overlayElement.style.transform = 'translateY(-10px) scale(0.98)';\n\n this.attachOptionsComponent();\n\n // Run the entrance animation after component is attached\n requestAnimationFrame(() => {\n overlayElement.style.transition = 'opacity 200ms cubic-bezier(0.25, 0.8, 0.25, 1), transform 200ms cubic-bezier(0.25, 0.8, 0.25, 1)';\n overlayElement.style.opacity = '1';\n overlayElement.style.transform = 'translateY(0) scale(1)';\n });\n }\n\n private openFullscreenDropdown(): void {\n const positionStrategy = this.overlay.position()\n .global()\n .centerHorizontally()\n .bottom();\n\n this.overlayRef = this.overlay.create({\n positionStrategy,\n width: '100%',\n height: '75vh',\n scrollStrategy: this.overlay.scrollStrategies.block(),\n hasBackdrop: true,\n backdropClass: 'cdk-overlay-dark-backdrop',\n panelClass: ['dropdown-overlay-fullscreen', 'dropdown-animated-fullscreen']\n });\n\n // Apply fullscreen entrance animation - slide from bottom\n const overlayElement = this.overlayRef.overlayElement;\n overlayElement.style.opacity = '0';\n overlayElement.style.transform = 'translateY(100%)';\n\n // Add overflow hidden to prevent horizontal scrolling\n overlayElement.style.overflowX = 'hidden';\n\n this.attachOptionsComponent(true);\n\n // Run the entrance animation after component is attached\n requestAnimationFrame(() => {\n overlayElement.style.transition = 'opacity 300ms cubic-bezier(0.25, 0.8, 0.25, 1), transform 300ms cubic-bezier(0.25, 0.8, 0.25, 1)';\n overlayElement.style.opacity = '1';\n overlayElement.style.transform = 'translateY(0)';\n });\n }\n\n private attachOptionsComponent(isFullscreen = false): void {\n // Create and attach the dropdown options component\n const optionsPortal = new ComponentPortal(KcDropdownOptionsComponent);\n const optionsRef = this.overlayRef!.attach(optionsPortal);\n \n // Configure options component\n optionsRef.instance.options = this.options();\n optionsRef.instance.filteredOptions = [...this.options()];\n optionsRef.instance.selectedValue = this._value;\n optionsRef.instance.variant = this.variant();\n optionsRef.instance.optionsEllipsis = this.optionsEllipsis();\n optionsRef.instance.isFullscreenSelection = isFullscreen;\n \n // Set search-related properties\n optionsRef.instance.isSearchable = this.isSearchable();\n optionsRef.instance.searchField = this.searchField();\n \n // Pass the custom template\n optionsRef.instance.customTemplate = this.optionTemplate;\n \n // Handle option selection\n optionsRef.instance.optionSelected.subscribe((option: DropdownOption) => {\n this.setValue(option.value);\n this.closeDropdown();\n });\n \n // Handle close button click for fullscreen mode\n optionsRef.instance.closeRequested?.subscribe(() => {\n this.closeDropdown();\n });\n \n // Handle backdrop click to close dropdown\n this.overlayRef!.backdropClick().pipe(\n takeUntil(this.destroy$)\n ).subscribe(() => {\n this.closeDropdown();\n });\n }\n\n closeDropdown(): void {\n if (this.overlayRef) {\n // Apply exit animation based on mode\n const overlayElement = this.overlayRef.overlayElement;\n\n if (this.isFullscreenSelection()) {\n // Fullscreen exit animation - slide down\n overlayElement.style.transition = 'opacity 250ms cubic-bezier(0.4, 0.0, 0.2, 1), transform 250ms cubic-bezier(0.4, 0.0, 0.2, 1)';\n overlayElement.style.opacity = '0';\n overlayElement.style.transform = 'translateY(100%)';\n } else {\n // Regular exit animation\n overlayElement.style.transition = 'opacity 180ms cubic-bezier(0.4, 0.0, 0.2, 1), transform 180ms cubic-bezier(0.4, 0.0, 0.2, 1)';\n overlayElement.style.opacity = '0';\n overlayElement.style.transform = 'translateY(-10px) scale(0.98)';\n }\n\n // Dispose after animation completes\n setTimeout(() => {\n if (this.overlayRef) {\n this.overlayRef.dispose();\n this.overlayRef = null;\n }\n }, this.isFullscreenSelection() ? 250 : 180);\n }\n\n this.isOpen = false;\n }\n\n setValue(value: any): void {\n if (this._value !== value) {\n this._value = value;\n this.onChange(value);\n this.valueChange.emit(value);\n }\n }\n\n // ControlValueAccessor implementation\n writeValue(value: any): void {\n this._value = value;\n }\n\n registerOnChange(fn: any): void {\n this.onChange = fn;\n }\n\n registerOnTouched(fn: any): void {\n this.onTouched = fn;\n }\n\n setDisabledState(isDisabled: boolean): void {\n this._internalDisabled = isDisabled;\n }\n\n ngOnDestroy(): void {\n this.destroy$.next();\n this.destroy$.complete();\n\n if (this.overlayRef) {\n this.overlayRef.dispose();\n }\n }\n}\n","<div\n class=\"dropdown-select\"\n [ngClass]=\"[\n size(),\n variant(),\n isComponentDisabled() ? 'disabled' : '',\n isFullWidth() ? 'full-width' : '',\n getTypographyClass()\n ]\"\n (click)=\"toggleDropdown()\"\n #dropdownTrigger>\n <div class=\"dropdown-value\">\n <kc-icon *ngIf=\"icon()\" [iconClass]=\"icon()\" [iconSize]=\"size()\" class=\"dropdown-icon\"></kc-icon>\n <ng-container *ngIf=\"prefixTemplateRef\">\n <ng-container [ngTemplateOutlet]=\"prefixTemplateRef\"></ng-container>\n </ng-container>\n <span>{{ getDisplayValue() }}</span>\n </div>\n <div *ngIf=\"showToggleIcon()\" class=\"dropdown-arrow\" [class.open]=\"isOpen\">\n <svg width=\"10\" height=\"6\" viewBox=\"0 0 10 6\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M1 1L5 5L9 1\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n </svg>\n </div>\n</div> ","import { Component, EventEmitter, Input, Output, TemplateRef, OnInit, OnDestroy, ContentChild } from '@angular/core';\nimport { CommonModule, NgClass } from '@angular/common';\nimport { FormControl, ReactiveFormsModule } from '@angular/forms';\nimport { Subject, debounceTime, takeUntil } from 'rxjs';\nimport { DropdownOption } from '../../dropdown-select/dropdown-select.models';\nimport { debounceTime as rxjsDebounceTime, distinctUntilChanged } from 'rxjs/operators';\nimport { ComponentSize } from '../../../types/sizing.type';\nimport { KcCheckboxComponent } from '../../checkbox/checkbox.component';\nimport { KcButtonComponent } from '../../button/button.component';\nimport { trigger, state, style, animate, transition } from '@angular/animations';\n\n@Component({\n selector: 'kc-dropdown-multiselect-options',\n standalone: true,\n imports: [CommonModule, NgClass, ReactiveFormsModule, KcCheckboxComponent, KcButtonComponent],\n templateUrl: './dropdown-multiselect-options.component.html',\n styleUrls: ['./dropdown-multiselect-options.component.scss'],\n animations: [\n trigger('enterLeave', [\n state('visible', style({\n opacity: 1,\n overflow: 'hidden',\n maxWidth: '100px',\n maxHeight: '32px',\n margin: '0 0 0 8px'\n })),\n state('hidden', style({\n opacity: 0,\n overflow: 'hidden',\n maxWidth: '0px',\n maxHeight: '0px',\n margin: '0',\n padding: '0'\n })),\n transition('hidden => visible', [\n animate('300ms ease-in-out')\n ]),\n transition('visible => hidden', [\n animate('300ms ease-in-out')\n ])\n ]),\n trigger('headerAnimation', [\n state('void', style({\n opacity: 0,\n height: '0px',\n minHeight: '0px',\n padding: '0px 16px',\n overflow: 'hidden',\n borderBottomWidth: '0px'\n })),\n state('*', style({\n opacity: 1,\n height: '*',\n minHeight: '42px',\n padding: '12px 16px',\n overflow: 'hidden',\n borderBottomWidth: '1px'\n })),\n transition('void <=> *', [\n animate('200ms ease-in-out')\n ])\n ])\n ]\n})\nexport class KcDropdownMultiselectOptionsComponent implements OnInit, OnDestroy {\n options: DropdownOption[] = [];\n filteredOptions: DropdownOption[] = [];\n selectedValues: any[] = [];\n variant: string = 'secondary';\n optionsEllipsis: boolean = false;\n isFullscreenSelection: boolean = false;\n isSearchable: boolean = false;\n searchField: string = 'label';\n minSelection: number = 0;\n maxSelection: number = Infinity;\n customTemplate: TemplateRef<any> | null = null;\n @ContentChild('emptyStateTemplate') emptyStateTemplate: TemplateRef<any> | null = null;\n size: ComponentSize = 'md';\n \n searchControl = new FormControl('');\n private destroy$ = new Subject<void>();\n \n @Output() optionSelected = new EventEmitter<DropdownOption>();\n @Output() closeRequested = new EventEmitter<void>();\n @Output() searchChanged = new EventEmitter<string>();\n @Output() clearAllRequested = new EventEmitter<void>();\n\n ngOnInit(): void {\n this.filteredOptions = [...this.options];\n \n if (this.isSearchable) {\n this.searchControl.valueChanges.pipe(\n debounceTime(128),\n takeUntil(this.destroy$)\n ).subscribe(value => {\n const searchTerm = value || '';\n this.filterOptions(searchTerm);\n this.searchChanged.emit(searchTerm);\n });\n }\n }\n \n filterOptions(searchText: string): void {\n if (!searchText.trim()) {\n this.filteredOptions = [...this.options];\n return;\n }\n \n const searchLower = searchText.toLowerCase();\n this.filteredOptions = this.options.filter(option => {\n const field = this.searchField === 'label' ? option.label : \n (option as any)[this.searchField] || '';\n return String(field).toLowerCase().includes(searchLower);\n });\n }\n\n isSelected(option: DropdownOption): boolean {\n return this.selectedValues.includes(option.value);\n }\n\n selectOption(option: DropdownOption): void {\n this.optionSelected.emit(option);\n \n // Immediately update the local selection state for UI feedback\n const isCurrentlySelected = this.isSelected(option);\n if (isCurrentlySelected) {\n this.selectedValues = this.selectedValues.filter(val => val !== option.value);\n } else {\n if (this.selectedValues.length < this.maxSelection || this.maxSelection === Infinity) {\n this.selectedValues = [...this.selectedValues, option.value];\n }\n }\n }\n \n clearAll(): void {\n this.clearAllRequested.emit();\n // Immediately update the local state for visual feedback\n this.selectedValues = [];\n }\n \n closeFullscreen(): void {\n this.closeRequested.emit();\n }\n \n isMaxSelectionsReached(): boolean {\n return this.selectedValues.length >= this.maxSelection && this.maxSelection !== Infinity;\n }\n \n getTypographyClass(): string {\n const typographyMap: Record<ComponentSize, string> = {\n 'xs': 'typo-text-1',\n 'sm': 'typo-text-2',\n 'md': 'typo-text-3',\n 'lg': 'typo-text-4',\n 'xlg': 'typo-text-5'\n };\n \n return typographyMap[this.size];\n }\n \n ngOnDestroy(): void {\n this.destroy$.next();\n this.destroy$.complete();\n }\n} ","<div \r\n class=\"dropdown-options\" \r\n [ngClass]=\"[variant, isFullscreenSelection ? 'fullscreen' : '']\">\r\n \r\n <!-- Fullscreen header -->\r\n <div *ngIf=\"isFullscreenSelection\" class=\"fullscreen-header\">\r\n <div class=\"header-title typo-title-3\">Select Options</div>\r\n <button class=\"header-close\" (click)=\"closeFullscreen()\">\r\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\r\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"></line>\r\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"></line>\r\n </svg>\r\n </button>\r\n </div>\r\n \r\n <!-- Options actions container - only show when searchable or has selections -->\r\n <div *ngIf=\"isSearchable || selectedValues.length > 0\" \r\n [@headerAnimation]\r\n class=\"options-actions\">\r\n <!-- Search container (always visible) -->\r\n <div class=\"search-container\">\r\n <input \r\n *ngIf=\"isSearchable\"\r\n type=\"text\" \r\n class=\"search-input\"\r\n [formControl]=\"searchControl\"\r\n placeholder=\"Search...\"\r\n [ngClass]=\"getTypographyClass()\"\r\n autocomplete=\"off\">\r\n </div>\r\n \r\n <!-- Clear selections button with animation -->\r\n <div class=\"clear-all-btn-enter-exit\" [@enterLeave]=\"selectedValues.length > 0 ? 'visible' : 'hidden'\">\r\n <kc-button\r\n *ngIf=\"selectedValues.length > 0\" \r\n [text]=\"'Clear all'\"\r\n [role]=\"'neutral'\"\r\n [size]=\"'sm'\"\r\n (buttonClick)=\"clearAll()\"\r\n class=\"clear-all-btn\"\r\n ></kc-button>\r\n </div>\r\n </div>\r\n \r\n <!-- Options list with fixed height -->\r\n <div class=\"options-list-container\">\r\n <div class=\"options-list\">\r\n <ng-container *ngIf=\"filteredOptions.length > 0; else noResults\">\r\n <div \r\n *ngFor=\"let option of filteredOptions\"\r\n class=\"option-item\"\r\n [class.selected]=\"isSelected(option)\"\r\n [class.disabled]=\"!isSelected(option) && isMaxSelectionsReached()\"\r\n [class.ellipsis]=\"optionsEllipsis\"\r\n [ngClass]=\"getTypographyClass()\"\r\n (click)=\"!isMaxSelectionsReached() || isSelected(option) ? selectOption(option) : null\">\r\n \r\n <!-- Custom template if provided -->\r\n <ng-container *ngIf=\"customTemplate; else defaultTemplate\">\r\n <ng-container *ngTemplateOutlet=\"customTemplate; context: { \r\n $implicit: option, \r\n selected: isSelected(option)\r\n }\"></ng-container>\r\n </ng-container>\r\n \r\n <!-- Default template -->\r\n <ng-template #defaultTemplate>\r\n <kc-checkbox \r\n [isChecked]=\"isSelected(option)\"\r\n [size]=\"size\"\r\n [isDisabled]=\"!isSelected(option) && isMaxSelectionsReached()\"\r\n ></kc-checkbox>\r\n <span class=\"option-label\">{{ option.label }}</span>\r\n </ng-template>\r\n </div>\r\n </ng-container>\r\n \r\n <!-- No results message -->\r\n <ng-template #noResults>\r\n <div class=\"no-results\" [ngClass]=\"getTypographyClass()\">\r\n <!-- Use projected empty state template when provided -->\r\n <ng-content select=\"[emptyStateTemplate]\" *ngIf=\"emptyStateTemplate\"></ng-content>\r\n \r\n <!-- Default empty state template when no projection provided -->\r\n <ng-container *ngIf=\"!emptyStateTemplate\">\r\n <div class=\"typo-caption\">No options found</div>\r\n </ng-container>\r\n </div>\r\n </ng-template>\r\n </div>\r\n </div>\r\n \r\n <!-- Selection summary for fullscreen mode -->\r\n <div *ngIf=\"isFullscreenSelection\" class=\"selection-summary\">\r\n <div class=\"selection-info\">\r\n <span class=\"typo-text-2\" *ngIf=\"selectedValues.length > 0\">Selected: {{ selectedValues.length }}</span>\r\n <span *ngIf=\"maxSelection < 9999999\" class=\"typo-text-2\">(Max: {{ maxSelection }})</span>\r\n </div>\r\n <kc-button\r\n [text]=\"'Apply'\"\r\n [variant]=\"variant === 'tertiary' || variant === 'raw' ? 'primary' : (variant === 'primary' || variant === 'secondary' ? variant : 'primary')\"\r\n [size]=\"size\"\r\n [isDisabled]=\"selectedValues.length < minSelection\"\r\n (buttonClick)=\"closeFullscreen()\"\r\n ></kc-button>\r\n </div>\r\n</div> ","import { \n Component, \n ElementRef, \n OnDestroy, \n OnInit, \n ViewChild, \n computed, \n inject, \n input, \n output, \n ContentChild, \n TemplateRef, \n signal \n} from '@angular/core';\nimport { CommonModule, NgClass } from '@angular/common';\nimport { Overlay, OverlayModule, OverlayRef } from '@angular/cdk/overlay';\nimport { ComponentPortal } from '@angular/cdk/portal';\nimport { ControlValueAccessor, FormsModule, NG_VALUE_ACCESSOR, ReactiveFormsModule } from '@angular/forms';\nimport { Subject, takeUntil } from 'rxjs';\nimport { ResponsiveService } from '../../services/responsive.service';\n\nimport { KcDropdownMultiselectOptionsComponent } from './dropdown-multiselect-options/dropdown-multiselect-options.component';\nimport { DropdownMultiselectConfig } from './dropdown-multiselect.models';\nimport { DropdownOption } from '../dropdown-select/dropdown-select.models';\nimport { DropdownVariant } from '../../types/dropdown-variant.type';\nimport { ComponentSize } from '../../types/sizing.type';\nimport { KcIconComponent } from '../../../icons/icon/icon.component';\n\n@Component({\n selector: 'kc-dropdown-multiselect',\n standalone: true,\n imports: [CommonModule, NgClass, FormsModule, ReactiveFormsModule, OverlayModule, KcIconComponent],\n templateUrl: './dropdown-multiselect.component.html',\n styleUrls: ['./dropdown-mul