UNPKG

@js-smart/ng-kit

Version:

Reusable Angular components built with Angular Material and Bootstrap 5.x, Utility classes/functions for Date, Form and String operations

773 lines (765 loc) 187 kB
import * as i0 from '@angular/core'; import { inject, ChangeDetectorRef, input, signal, output, effect, ChangeDetectionStrategy, Component, viewChild, computed, forwardRef, ElementRef, Renderer2, DestroyRef, ApplicationRef, EnvironmentInjector, createComponent, Directive, DOCUMENT, HostListener, Input, Injectable, ViewContainerRef, Inject, InjectionToken, makeEnvironmentProviders, untracked } from '@angular/core'; import * as i1 from '@angular/material/progress-spinner'; import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; import { NgClass } from '@angular/common'; import * as i1$1 from '@angular/forms'; import { FormControl, ReactiveFormsModule, NG_VALUE_ACCESSOR, NgControl } from '@angular/forms'; import * as i2 from '@angular/material/autocomplete'; import { MatAutocompleteTrigger, MatAutocomplete, MatAutocompleteModule } from '@angular/material/autocomplete'; import * as i1$2 from '@angular/material/button'; import { MatButtonModule, MatIconButton, MatAnchor, MatButton } from '@angular/material/button'; import { MatFormFieldModule } from '@angular/material/form-field'; import * as i2$1 from '@angular/material/icon'; import { MatIconModule, MatIcon, MatIconRegistry } from '@angular/material/icon'; import * as i3 from '@angular/material/input'; import { MatInputModule } from '@angular/material/input'; import { Subject, throttleTime } from 'rxjs'; import { DomSanitizer } from '@angular/platform-browser'; import * as i1$3 from '@angular/material/dialog'; import { MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog'; import * as i3$1 from '@angular/material/divider'; import { MatDividerModule } from '@angular/material/divider'; import * as i1$4 from '@angular/material/snack-bar'; import { MAT_SNACK_BAR_DATA, MatSnackBar } from '@angular/material/snack-bar'; import { QueryClient, MutationObserver, QueryObserver } from '@tanstack/query-core'; /** * Boostrap Alert component that can be used to alert messages to the user * * @author Pavan Kumar Jadda * @since 12.0.0 */ class AlertComponent { constructor() { this.cdr = inject(ChangeDetectorRef); /** * Type of the BootStrap Alert. Following values are supported. See BootStrap docs for more information */ this.type = input('info', /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "type" }] : /* istanbul ignore next */ [])); /** * Is alert visible or open */ this.isOpen = input(true, /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "isOpen" }] : /* istanbul ignore next */ [])); /** * Writable signal for isOpen */ this.open = signal(this.isOpen(), /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ [])); /** * If set, displays an inline “Close” button */ this.dismissible = input(true, /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "dismissible" }] : /* istanbul ignore next */ [])); /** * If set, dismisses the alert after Dismiss Timeout */ this.dismissOnTimeout = input(true, /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "dismissOnTimeout" }] : /* istanbul ignore next */ [])); /** * Number in milliseconds, after which alert will be closed. Default value is 5000 ms */ this.dismissTimeout = input(5000, /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "dismissTimeout" }] : /* istanbul ignore next */ [])); /** * Additional classes to be added to the alert. This can be used to add custom styles to the alert */ this.class = input('', /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "class" }] : /* istanbul ignore next */ [])); /** * Emits when the alert is closed. */ this.closed = output(); // React to isOpen input changes effect(() => { this.open.set(this.isOpen()); }); } /** * Initialize the component and settings * * @author Pavan Kumar Jadda * @since 12.0.0 */ ngOnInit() { if (this.dismissOnTimeout()) { setTimeout(() => { this.closeAlert(); this.cdr.markForCheck(); }, this.dismissTimeout()); } } /** * Closes BootStrap Alert if not open * * @author Pavan Kumar Jadda * @since 12.0.0 */ closeAlert() { if (!this.open()) { return; } this.open.set(false); this.closed.emit(); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: AlertComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.5", type: AlertComponent, isStandalone: true, selector: "lib-alert, alert", inputs: { type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null }, isOpen: { classPropertyName: "isOpen", publicName: "isOpen", isSignal: true, isRequired: false, transformFunction: null }, dismissible: { classPropertyName: "dismissible", publicName: "dismissible", isSignal: true, isRequired: false, transformFunction: null }, dismissOnTimeout: { classPropertyName: "dismissOnTimeout", publicName: "dismissOnTimeout", isSignal: true, isRequired: false, transformFunction: null }, dismissTimeout: { classPropertyName: "dismissTimeout", publicName: "dismissTimeout", isSignal: true, isRequired: false, transformFunction: null }, class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { closed: "closed" }, ngImport: i0, template: "@if (open()) {\n\t<div class=\"row {{ class() }}\">\n\t\t<div class=\"col-xs-12 col-sm-12 col-md-auto mx-auto\">\n\t\t\t<div class=\"alert alert-{{ type() }} alert-dismissible alert_div\" role=\"alert\" aria-live=\"polite\">\n\t\t\t\t<ng-content></ng-content>\n\t\t\t\t@if (dismissible()) {\n\t\t\t\t\t<button (click)=\"closeAlert()\" aria-label=\"Close\" class=\"btn-close\" data-bs-dismiss=\"alert\" type=\"button\"></button>\n\t\t\t\t}\n\t\t\t</div>\n\t\t</div>\n\t</div>\n}\n", styles: [".alert_div{display:flex;align-items:center;justify-content:center}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: AlertComponent, decorators: [{ type: Component, args: [{ selector: 'lib-alert, alert', changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (open()) {\n\t<div class=\"row {{ class() }}\">\n\t\t<div class=\"col-xs-12 col-sm-12 col-md-auto mx-auto\">\n\t\t\t<div class=\"alert alert-{{ type() }} alert-dismissible alert_div\" role=\"alert\" aria-live=\"polite\">\n\t\t\t\t<ng-content></ng-content>\n\t\t\t\t@if (dismissible()) {\n\t\t\t\t\t<button (click)=\"closeAlert()\" aria-label=\"Close\" class=\"btn-close\" data-bs-dismiss=\"alert\" type=\"button\"></button>\n\t\t\t\t}\n\t\t\t</div>\n\t\t</div>\n\t</div>\n}\n", styles: [".alert_div{display:flex;align-items:center;justify-content:center}\n"] }] }], ctorParameters: () => [], propDecorators: { type: [{ type: i0.Input, args: [{ isSignal: true, alias: "type", required: false }] }], isOpen: [{ type: i0.Input, args: [{ isSignal: true, alias: "isOpen", required: false }] }], dismissible: [{ type: i0.Input, args: [{ isSignal: true, alias: "dismissible", required: false }] }], dismissOnTimeout: [{ type: i0.Input, args: [{ isSignal: true, alias: "dismissOnTimeout", required: false }] }], dismissTimeout: [{ type: i0.Input, args: [{ isSignal: true, alias: "dismissTimeout", required: false }] }], class: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }], closed: [{ type: i0.Output, args: ["closed"] }] } }); class SpinnerComponent { constructor() { /** * Use Boostrap Spinner. Default `true` */ this.bootstrapSpinner = input(true, /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "bootstrapSpinner" }] : /* istanbul ignore next */ [])); /** * Diameter of the Angular Material spinner */ this.diameter = input(50, /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "diameter" }] : /* istanbul ignore next */ [])); /** * Color of the Angular Material spinner */ this.color = input('primary', /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "color" }] : /* istanbul ignore next */ [])); /** * Stroke Width of the Angular Material spinner */ this.strokeWidth = input(5, /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "strokeWidth" }] : /* istanbul ignore next */ [])); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: SpinnerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.5", type: SpinnerComponent, isStandalone: true, selector: "spinner,lib-spinner", inputs: { bootstrapSpinner: { classPropertyName: "bootstrapSpinner", publicName: "bootstrapSpinner", isSignal: true, isRequired: false, transformFunction: null }, diameter: { classPropertyName: "diameter", publicName: "diameter", isSignal: true, isRequired: false, transformFunction: null }, color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: false, transformFunction: null }, strokeWidth: { classPropertyName: "strokeWidth", publicName: "strokeWidth", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<!-- Angular Material Spinner -->\n@if (!bootstrapSpinner()) {\n\t<mat-spinner [color]=\"color()\" [diameter]=\"diameter()\" [strokeWidth]=\"strokeWidth()\" class=\"mx-auto\"> </mat-spinner>\n}\n\n<!-- Bootstrap Spinner -->\n@if (bootstrapSpinner()) {\n\t<div class=\"d-flex justify-content-center\">\n\t\t<div class=\"spinner-border bs-spinner\" role=\"status\"></div>\n\t</div>\n}\n", styles: [".bs-spinner-sm{color:#3f51b5;margin-bottom:20px}.bs-spinner{color:#3f51b5;width:3rem;height:3rem;margin-bottom:20px}.bs-spinner-lg{color:#3f51b5;width:5rem;height:5rem;margin-bottom:20px}\n"], dependencies: [{ kind: "ngmodule", type: MatProgressSpinnerModule }, { kind: "component", type: i1.MatProgressSpinner, selector: "mat-progress-spinner, mat-spinner", inputs: ["color", "mode", "value", "diameter", "strokeWidth"], exportAs: ["matProgressSpinner"] }], changeDetection: i0.ChangeDetectionStrategy.Eager }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: SpinnerComponent, decorators: [{ type: Component, args: [{ selector: 'spinner,lib-spinner', imports: [MatProgressSpinnerModule], changeDetection: ChangeDetectionStrategy.Eager, template: "<!-- Angular Material Spinner -->\n@if (!bootstrapSpinner()) {\n\t<mat-spinner [color]=\"color()\" [diameter]=\"diameter()\" [strokeWidth]=\"strokeWidth()\" class=\"mx-auto\"> </mat-spinner>\n}\n\n<!-- Bootstrap Spinner -->\n@if (bootstrapSpinner()) {\n\t<div class=\"d-flex justify-content-center\">\n\t\t<div class=\"spinner-border bs-spinner\" role=\"status\"></div>\n\t</div>\n}\n", styles: [".bs-spinner-sm{color:#3f51b5;margin-bottom:20px}.bs-spinner{color:#3f51b5;width:3rem;height:3rem;margin-bottom:20px}.bs-spinner-lg{color:#3f51b5;width:5rem;height:5rem;margin-bottom:20px}\n"] }] }], propDecorators: { bootstrapSpinner: [{ type: i0.Input, args: [{ isSignal: true, alias: "bootstrapSpinner", required: false }] }], diameter: [{ type: i0.Input, args: [{ isSignal: true, alias: "diameter", required: false }] }], color: [{ type: i0.Input, args: [{ isSignal: true, alias: "color", required: false }] }], strokeWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "strokeWidth", required: false }] }] } }); /** * Reusable Auto Complete component that extends MatAutoComplete to show Clear icon and Arrow buttons * * @author Pavan Kumar Jadda * @since 12.0.0 */ class AutocompleteComponent { constructor() { /** Gets reference to the MatAutocompleteTrigger to programmatically open/close the panel */ this.autocompleteTrigger = viewChild.required(MatAutocompleteTrigger); /** MatAutocomplete instance (used to scroll the selected option into view when the panel opens) */ this.matAutocomplete = viewChild.required(MatAutocomplete); /** Gets reference to the input element for re-focusing after clear */ this.inputElement = viewChild.required('inputEl'); /** Label of the autocomplete form field */ this.label = input('Select Value', /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ [])); /** Placeholder text displayed inside the input when empty */ this.placeholder = input('', /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ [])); /** Appearance of the form field. Defaults to `outline` */ this.appearance = input('outline', /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "appearance" }] : /* istanbul ignore next */ [])); /** List of CSS classes to apply to the form field */ this.classes = input('', /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "classes" }] : /* istanbul ignore next */ [])); /** List of options to display in the dropdown */ this.options = input([], /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ [])); /** * Function that maps an option to its display string. * Used for rendering options in the dropdown and showing the selected value in the input. * Defaults to `String(value)` which works for primitive string options. */ this.displayWith = input((value) => String(value), /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "displayWith" }] : /* istanbul ignore next */ [])); /** Whether the autocomplete is in a loading state. Shows a spinner instead of options */ this.loading = input(false, /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ [])); /** Text displayed when the autocomplete is in a loading state. Defaults to 'Loading...' */ this.loadingText = input('Loading...', /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "loadingText" }] : /* istanbul ignore next */ [])); /** Text displayed when no options match the filter input. Defaults to 'No values found' */ this.noOptionsText = input('No values found', /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "noOptionsText" }] : /* istanbul ignore next */ [])); /** Emits the selected value when an option is picked from the dropdown */ this.selectionChange = output(); /** Emits the raw text in the input on each keystroke (filter text while typing) */ this.onInputChange = output(); /** Internal form control for the autocomplete input */ this.control = new FormControl(null); /** Signal that tracks whether the autocomplete panel is currently open */ this.isExpanded = signal(false, /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "isExpanded" }] : /* istanbul ignore next */ [])); /** Signal that tracks the current filter text typed by the user */ this.filterText = signal('', /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "filterText" }] : /* istanbul ignore next */ [])); /** * Computed signal that filters options based on the current filter text. * Uses the `displayWith` function to extract searchable text from each option. * Returns all options when filter text is empty. */ this.filteredOptions = computed(() => { const filterValue = this.filterText().toLowerCase(); const displayFn = this.displayWith(); return this.options().filter((option) => displayFn(option).toLowerCase().includes(filterValue)); }, /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "filteredOptions" }] : /* istanbul ignore next */ [])); /** * Display function passed to mat-autocomplete's [displayWith] to render the * selected value in the input field. Returns empty string for null/undefined values. */ this.displayFn = (value) => { if (value == null || value === '') { return ''; } return this.displayWith()(value); }; /** Callback function registered by the parent form to propagate value changes */ this.onChange = (_) => { /* noop */ }; /** Callback function registered by the parent form to propagate touched state */ this.onTouched = () => { /* noop */ }; } /** * Writes a new value to the internal control. Called by the parent form * when the form control value is set programmatically. */ writeValue(value) { // Use null (not '') so MatAutocompleteTrigger clears mat-option selection and stays in sync with the parent CVA. const internal = value == null ? null : value; this.control.setValue(internal, { emitEvent: false }); } /** * Registers a callback function that is called when the control's value * changes in the UI (option selected or input cleared). */ registerOnChange(fn) { this.onChange = fn; } /** * Registers a callback function that is called when the control is * first interacted with (blur or selection). */ registerOnTouched(fn) { this.onTouched = fn; } /** * Sets the disabled state of the internal control. Called by the parent form * when `control.disable()` or `control.enable()` is invoked. */ setDisabledState(isDisabled) { if (isDisabled) { this.control.disable(); } else { this.control.enable(); } } /** Updates the filter text signal as the user types in the input */ onInput(event) { const value = event.target.value; this.filterText.set(value); this.onInputChange.emit(value); } /** * Clears the input value, resets the filter, notifies the parent form, * and re-focuses the input element. */ clearInput(event) { event.preventDefault(); event.stopPropagation(); this.autocompleteTrigger().closePanel(); this.control.setValue(null, { emitEvent: false }); this.filterText.set(''); this.onChange(null); this.onTouched(); queueMicrotask(() => this.inputElement().nativeElement.focus()); } /** Runs when the autocomplete overlay opens: keep expanded state and scroll the selected option into view. */ onPanelOpened() { this.isExpanded.set(true); this.scrollSelectedOptionIntoView(); } scrollSelectedOptionIntoView() { requestAnimationFrame(() => { requestAnimationFrame(() => { const panelEl = this.matAutocomplete().panel?.nativeElement; if (!panelEl) { return; } const selected = panelEl.querySelector('.mat-mdc-option.mdc-list-item--selected'); selected?.scrollIntoView({ block: 'nearest', inline: 'nearest' }); }); }); } /** Opens the autocomplete panel programmatically */ openPanel() { this.autocompleteTrigger().openPanel(); } /** * Handles option selection from the dropdown. Resets the filter text, * notifies the parent form of the new value, and emits the selectionChange output. */ onOptionSelected(value) { this.filterText.set(''); this.onChange(value); this.onTouched(); this.selectionChange.emit(value); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: AutocompleteComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.5", type: AutocompleteComponent, isStandalone: true, selector: "autocomplete, lib-autocomplete", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, appearance: { classPropertyName: "appearance", publicName: "appearance", isSignal: true, isRequired: false, transformFunction: null }, classes: { classPropertyName: "classes", publicName: "classes", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, displayWith: { classPropertyName: "displayWith", publicName: "displayWith", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, loadingText: { classPropertyName: "loadingText", publicName: "loadingText", isSignal: true, isRequired: false, transformFunction: null }, noOptionsText: { classPropertyName: "noOptionsText", publicName: "noOptionsText", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectionChange: "selectionChange", onInputChange: "onInputChange" }, providers: [ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => AutocompleteComponent), multi: true, }, ], viewQueries: [{ propertyName: "autocompleteTrigger", first: true, predicate: MatAutocompleteTrigger, descendants: true, isSignal: true }, { propertyName: "matAutocomplete", first: true, predicate: MatAutocomplete, descendants: true, isSignal: true }, { propertyName: "inputElement", first: true, predicate: ["inputEl"], descendants: true, isSignal: true }], ngImport: i0, template: "<mat-form-field\n\t#origin=\"matAutocompleteOrigin\"\n\t[appearance]=\"appearance()\"\n\t[ngClass]=\"classes()\"\n\tmatAutocompleteOrigin\n\tsubscriptSizing=\"dynamic\">\n\t<mat-label>{{ label() }}</mat-label>\n\n\t<input\n\t\t#inputEl\n\t\t#trigger=\"matAutocompleteTrigger\"\n\t\t(click)=\"trigger.openPanel()\"\n\t\t(input)=\"onInput($event)\"\n\t\t[formControl]=\"control\"\n\t\t[matAutocompleteConnectedTo]=\"origin\"\n\t\t[matAutocomplete]=\"auto\"\n\t\t[placeholder]=\"placeholder()\"\n\t\tmatInput\n\t\ttype=\"text\" />\n\n\t<div class=\"autocomplete-suffix\" matSuffix>\n\t\t@if (control.value && !control.disabled) {\n\t\t\t<button type=\"button\" (click)=\"clearInput($event)\" aria-label=\"Clear value\" class=\"suffix-action-btn\" mat-icon-button>\n\t\t\t\t<mat-icon>close</mat-icon>\n\t\t\t</button>\n\t\t} @else if (!control.disabled) {\n\t\t\t<span aria-hidden=\"true\" class=\"suffix-placeholder\"></span>\n\t\t}\n\t\t@if (!control.disabled) {\n\t\t\t<button\n\t\t\t\t(click)=\"trigger.panelOpen ? trigger.closePanel() : trigger.openPanel(); $event.stopPropagation()\"\n\t\t\t\t[attr.aria-label]=\"trigger.panelOpen ? 'Close options' : 'Open options'\"\n\t\t\t\tclass=\"suffix-action-btn\"\n\t\t\t\tmat-icon-button\n\t\t\t\ttype=\"button\">\n\t\t\t\t@if (isExpanded()) {\n\t\t\t\t\t<mat-icon>arrow_drop_up</mat-icon>\n\t\t\t\t} @else {\n\t\t\t\t\t<mat-icon>arrow_drop_down</mat-icon>\n\t\t\t\t}\n\t\t\t</button>\n\t\t}\n\t</div>\n\n\t<mat-autocomplete\n\t\t#auto=\"matAutocomplete\"\n\t\t(closed)=\"isExpanded.set(false)\"\n\t\t(opened)=\"onPanelOpened()\"\n\t\t(optionSelected)=\"onOptionSelected($event.option.value)\"\n\t\t[displayWith]=\"displayFn\">\n\t\t@if (loading()) {\n\t\t\t<mat-option disabled>\n\t\t\t\t<div style=\"display: flex; align-items: center; gap: 8px\">\n\t\t\t\t\t<mat-spinner diameter=\"20\"></mat-spinner>\n\t\t\t\t\t<span>{{ loadingText() }}</span>\n\t\t\t\t</div>\n\t\t\t</mat-option>\n\t\t} @else {\n\t\t\t@for (option of filteredOptions(); track option) {\n\t\t\t\t<mat-option [value]=\"option\">{{ displayWith()(option) }}</mat-option>\n\t\t\t} @empty {\n\t\t\t\t<mat-option disabled>{{ noOptionsText() }}</mat-option>\n\t\t\t}\n\t\t}\n\t</mat-autocomplete>\n</mat-form-field>\n", styles: [".autocomplete-suffix{display:flex;align-items:center}.suffix-action-btn{width:40px;height:40px;padding:0}.suffix-placeholder{display:inline-block;width:40px}\n"], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: MatAutocompleteModule }, { kind: "component", type: i2.MatAutocomplete, selector: "mat-autocomplete", inputs: ["aria-label", "aria-labelledby", "displayWith", "autoActiveFirstOption", "autoSelectActiveOption", "requireSelection", "panelWidth", "disableRipple", "class", "hideSingleSelectionIndicator"], outputs: ["optionSelected", "opened", "closed", "optionActivated"], exportAs: ["matAutocomplete"] }, { kind: "component", type: i2.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "directive", type: i2.MatAutocompleteTrigger, selector: "input[matAutocomplete], textarea[matAutocomplete]", inputs: ["matAutocomplete", "matAutocompletePosition", "matAutocompleteConnectedTo", "autocomplete", "matAutocompleteDisabled"], exportAs: ["matAutocompleteTrigger"] }, { kind: "directive", type: i2.MatAutocompleteOrigin, selector: "[matAutocompleteOrigin]", exportAs: ["matAutocompleteOrigin"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i3.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "component", type: i3.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i3.MatLabel, selector: "mat-label" }, { kind: "directive", type: i3.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1$2.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatProgressSpinnerModule }, { kind: "component", type: i1.MatProgressSpinner, selector: "mat-progress-spinner, mat-spinner", inputs: ["color", "mode", "value", "diameter", "strokeWidth"], exportAs: ["matProgressSpinner"] }], changeDetection: i0.ChangeDetectionStrategy.Eager }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: AutocompleteComponent, decorators: [{ type: Component, args: [{ selector: 'autocomplete, lib-autocomplete', imports: [ NgClass, ReactiveFormsModule, MatAutocompleteModule, MatInputModule, MatFormFieldModule, MatIconModule, MatButtonModule, MatProgressSpinnerModule, ], changeDetection: ChangeDetectionStrategy.Eager, providers: [ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => AutocompleteComponent), multi: true, }, ], template: "<mat-form-field\n\t#origin=\"matAutocompleteOrigin\"\n\t[appearance]=\"appearance()\"\n\t[ngClass]=\"classes()\"\n\tmatAutocompleteOrigin\n\tsubscriptSizing=\"dynamic\">\n\t<mat-label>{{ label() }}</mat-label>\n\n\t<input\n\t\t#inputEl\n\t\t#trigger=\"matAutocompleteTrigger\"\n\t\t(click)=\"trigger.openPanel()\"\n\t\t(input)=\"onInput($event)\"\n\t\t[formControl]=\"control\"\n\t\t[matAutocompleteConnectedTo]=\"origin\"\n\t\t[matAutocomplete]=\"auto\"\n\t\t[placeholder]=\"placeholder()\"\n\t\tmatInput\n\t\ttype=\"text\" />\n\n\t<div class=\"autocomplete-suffix\" matSuffix>\n\t\t@if (control.value && !control.disabled) {\n\t\t\t<button type=\"button\" (click)=\"clearInput($event)\" aria-label=\"Clear value\" class=\"suffix-action-btn\" mat-icon-button>\n\t\t\t\t<mat-icon>close</mat-icon>\n\t\t\t</button>\n\t\t} @else if (!control.disabled) {\n\t\t\t<span aria-hidden=\"true\" class=\"suffix-placeholder\"></span>\n\t\t}\n\t\t@if (!control.disabled) {\n\t\t\t<button\n\t\t\t\t(click)=\"trigger.panelOpen ? trigger.closePanel() : trigger.openPanel(); $event.stopPropagation()\"\n\t\t\t\t[attr.aria-label]=\"trigger.panelOpen ? 'Close options' : 'Open options'\"\n\t\t\t\tclass=\"suffix-action-btn\"\n\t\t\t\tmat-icon-button\n\t\t\t\ttype=\"button\">\n\t\t\t\t@if (isExpanded()) {\n\t\t\t\t\t<mat-icon>arrow_drop_up</mat-icon>\n\t\t\t\t} @else {\n\t\t\t\t\t<mat-icon>arrow_drop_down</mat-icon>\n\t\t\t\t}\n\t\t\t</button>\n\t\t}\n\t</div>\n\n\t<mat-autocomplete\n\t\t#auto=\"matAutocomplete\"\n\t\t(closed)=\"isExpanded.set(false)\"\n\t\t(opened)=\"onPanelOpened()\"\n\t\t(optionSelected)=\"onOptionSelected($event.option.value)\"\n\t\t[displayWith]=\"displayFn\">\n\t\t@if (loading()) {\n\t\t\t<mat-option disabled>\n\t\t\t\t<div style=\"display: flex; align-items: center; gap: 8px\">\n\t\t\t\t\t<mat-spinner diameter=\"20\"></mat-spinner>\n\t\t\t\t\t<span>{{ loadingText() }}</span>\n\t\t\t\t</div>\n\t\t\t</mat-option>\n\t\t} @else {\n\t\t\t@for (option of filteredOptions(); track option) {\n\t\t\t\t<mat-option [value]=\"option\">{{ displayWith()(option) }}</mat-option>\n\t\t\t} @empty {\n\t\t\t\t<mat-option disabled>{{ noOptionsText() }}</mat-option>\n\t\t\t}\n\t\t}\n\t</mat-autocomplete>\n</mat-form-field>\n", styles: [".autocomplete-suffix{display:flex;align-items:center}.suffix-action-btn{width:40px;height:40px;padding:0}.suffix-placeholder{display:inline-block;width:40px}\n"] }] }], propDecorators: { autocompleteTrigger: [{ type: i0.ViewChild, args: [i0.forwardRef(() => MatAutocompleteTrigger), { isSignal: true }] }], matAutocomplete: [{ type: i0.ViewChild, args: [i0.forwardRef(() => MatAutocomplete), { isSignal: true }] }], inputElement: [{ type: i0.ViewChild, args: ['inputEl', { isSignal: true }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], appearance: [{ type: i0.Input, args: [{ isSignal: true, alias: "appearance", required: false }] }], classes: [{ type: i0.Input, args: [{ isSignal: true, alias: "classes", required: false }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], displayWith: [{ type: i0.Input, args: [{ isSignal: true, alias: "displayWith", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], loadingText: [{ type: i0.Input, args: [{ isSignal: true, alias: "loadingText", required: false }] }], noOptionsText: [{ type: i0.Input, args: [{ isSignal: true, alias: "noOptionsText", required: false }] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }], onInputChange: [{ type: i0.Output, args: ["onInputChange"] }] } }); class AutocompleteClearButtonComponent { constructor() { this.clicked = output(); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: AutocompleteClearButtonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.0.5", type: AutocompleteClearButtonComponent, isStandalone: true, selector: "autocomplete-clear-button", outputs: { clicked: "clicked" }, ngImport: i0, template: ` <button mat-icon-button type="button" aria-label="Clear value" style="padding: 0 !important; width: 32px; height: 32px;" (click)="clicked.emit($event)"> <mat-icon>close</mat-icon> </button> `, isInline: true, dependencies: [{ kind: "component", type: MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }], changeDetection: i0.ChangeDetectionStrategy.Eager }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: AutocompleteClearButtonComponent, decorators: [{ type: Component, args: [{ selector: 'autocomplete-clear-button', standalone: true, imports: [MatIconButton, MatIcon], changeDetection: ChangeDetectionStrategy.Eager, template: ` <button mat-icon-button type="button" aria-label="Clear value" style="padding: 0 !important; width: 32px; height: 32px;" (click)="clicked.emit($event)"> <mat-icon>close</mat-icon> </button> `, }] }], propDecorators: { clicked: [{ type: i0.Output, args: ["clicked"] }] } }); class AutocompleteDropdownButtonComponent { constructor() { this.expanded = signal(false, /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "expanded" }] : /* istanbul ignore next */ [])); this.clicked = output(); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: AutocompleteDropdownButtonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.0.5", type: AutocompleteDropdownButtonComponent, isStandalone: true, selector: "autocomplete-dropdown-button", outputs: { clicked: "clicked" }, ngImport: i0, template: ` <button mat-icon-button type="button" style="padding: 0 !important; width: 32px; height: 32px;" [attr.aria-label]="expanded() ? 'Close options' : 'Open options'" (click)="clicked.emit($event)"> <mat-icon>{{ expanded() ? 'arrow_drop_up' : 'arrow_drop_down' }}</mat-icon> </button> `, isInline: true, dependencies: [{ kind: "component", type: MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }], changeDetection: i0.ChangeDetectionStrategy.Eager }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: AutocompleteDropdownButtonComponent, decorators: [{ type: Component, args: [{ selector: 'autocomplete-dropdown-button', standalone: true, imports: [MatIconButton, MatIcon], changeDetection: ChangeDetectionStrategy.Eager, template: ` <button mat-icon-button type="button" style="padding: 0 !important; width: 32px; height: 32px;" [attr.aria-label]="expanded() ? 'Close options' : 'Open options'" (click)="clicked.emit($event)"> <mat-icon>{{ expanded() ? 'arrow_drop_up' : 'arrow_drop_down' }}</mat-icon> </button> `, }] }], propDecorators: { clicked: [{ type: i0.Output, args: ["clicked"] }] } }); /** * Single directive that adds clear and dropdown-toggle icon buttons to a * Material autocomplete input. Unlike {@link LibAutocompleteActionsDirective}, * this directive is applied directly to the `input` element and does not require * an anchor directive or a separate suffix component. * * Usage: * ```html * <mat-form-field appearance="outline"> * <mat-label>City</mat-label> * <input matInput [matAutocomplete]="auto" formControlName="city" autocompleteSuffix /> * <mat-autocomplete #auto="matAutocomplete"> … </mat-autocomplete> * </mat-form-field> * ``` * * @author Pavan Kumar Jadda * @since 21.8.0 */ class AutocompleteSuffixDirective { constructor() { this.trigger = inject(MatAutocompleteTrigger); this.el = inject((ElementRef)); this.renderer = inject(Renderer2); this.destroyRef = inject(DestroyRef); this.ngControl = inject(NgControl, { optional: true, self: true }); this.appRef = inject(ApplicationRef); this.envInjector = inject(EnvironmentInjector); this.clearRef = null; this.dropdownRef = null; this.isExpanded = false; this.unlisten = []; } ngAfterViewInit() { this.createSuffixElements(); this.setupListeners(); this.updateClearButtonVisibility(); } createSuffixElements() { const formField = this.el.nativeElement.closest('mat-form-field'); if (!formField) { return; } const flexContainer = formField.querySelector('.mat-mdc-form-field-flex'); if (!flexContainer) { return; } let suffixWrapper = flexContainer.querySelector('.mat-mdc-form-field-icon-suffix'); if (!suffixWrapper) { suffixWrapper = this.renderer.createElement('div'); this.renderer.addClass(suffixWrapper, 'mat-mdc-form-field-icon-suffix'); this.renderer.setAttribute(suffixWrapper, 'data-mat-icon-type', 'font'); this.renderer.appendChild(flexContainer, suffixWrapper); } const container = this.renderer.createElement('div'); this.renderer.setStyle(container, 'display', 'flex'); this.renderer.setStyle(container, 'align-items', 'center'); this.clearRef = createComponent(AutocompleteClearButtonComponent, { environmentInjector: this.envInjector }); this.appRef.attachView(this.clearRef.hostView); this.clearRef.instance.clicked.subscribe((e) => this.clearInput(e)); this.renderer.setStyle(this.clearRef.location.nativeElement, 'visibility', 'hidden'); this.dropdownRef = createComponent(AutocompleteDropdownButtonComponent, { environmentInjector: this.envInjector }); this.appRef.attachView(this.dropdownRef.hostView); this.dropdownRef.instance.clicked.subscribe((e) => this.togglePanel(e)); this.renderer.appendChild(container, this.clearRef.location.nativeElement); this.renderer.appendChild(container, this.dropdownRef.location.nativeElement); this.renderer.appendChild(suffixWrapper, container); this.destroyRef.onDestroy(() => { this.clearRef?.destroy(); this.dropdownRef?.destroy(); }); } setupListeners() { this.unlisten.push(this.renderer.listen(this.el.nativeElement, 'input', () => this.updateClearButtonVisibility())); const autocomplete = this.trigger.autocomplete; const openedSub = autocomplete.opened.subscribe(() => { this.isExpanded = true; this.updateDropdownIcon(); this.scrollSelectedOptionIntoView(); }); const closedSub = autocomplete.closed.subscribe(() => { this.isExpanded = false; this.updateDropdownIcon(); this.updateClearButtonVisibility(); }); const selectedSub = autocomplete.optionSelected.subscribe(() => this.updateClearButtonVisibility()); let valueChangesSub = null; if (this.ngControl?.control) { valueChangesSub = this.ngControl.control.valueChanges.subscribe(() => this.updateClearButtonVisibility()); } this.destroyRef.onDestroy(() => { this.unlisten.forEach((fn) => fn()); openedSub.unsubscribe(); closedSub.unsubscribe(); selectedSub.unsubscribe(); valueChangesSub?.unsubscribe(); }); } clearInput(event) { event.preventDefault(); event.stopPropagation(); this.trigger.closePanel(); this.el.nativeElement.value = ''; if (this.ngControl?.control) { this.ngControl.control.setValue(null, { emitEvent: true }); this.ngControl.control.markAsTouched(); } this.updateClearButtonVisibility(); queueMicrotask(() => this.el.nativeElement.focus()); } togglePanel(event) { event.stopPropagation(); if (this.trigger.panelOpen) { this.trigger.closePanel(); } else { this.trigger.openPanel(); } } updateClearButtonVisibility() { const hasValue = !!(this.el.nativeElement.value || this.ngControl?.control?.value); if (this.clearRef) { this.renderer.setStyle(this.clearRef.location.nativeElement, 'visibility', hasValue ? 'visible' : 'hidden'); } } scrollSelectedOptionIntoView() { requestAnimationFrame(() => { requestAnimationFrame(() => { const panelEl = this.trigger.autocomplete.panel?.nativeElement; if (!panelEl) { return; } const selected = panelEl.querySelector('.mat-mdc-option.mdc-list-item--selected'); selected?.scrollIntoView({ block: 'nearest', inline: 'nearest' }); }); }); } updateDropdownIcon() { if (this.dropdownRef) { this.dropdownRef.instance.expanded.set(this.isExpanded); } } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: AutocompleteSuffixDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.5", type: AutocompleteSuffixDirective, isStandalone: true, selector: "input[matAutocomplete][autocompleteSuffix]", ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: AutocompleteSuffixDirective, decorators: [{ type: Directive, args: [{ selector: 'input[matAutocomplete][autocompleteSuffix]', standalone: true, }] }] }); class PrintOptions { constructor(options) { this.printSectionId = ''; this.printTitle = ''; this.useExistingCss = false; this.bodyClass = ''; this.openNewTab = false; this.previewOnly = false; this.closeWindow = true; this.printDelay = 0; if (options) { Object.assign(this, options); } } } /** * Reusable Angular directory that prints given contents of HTML element * * @since 12.0.0 * @author Pavan Kumar Jadda */ class NgxPrintDirective { constructor() { this.document = inject(DOCUMENT); /** * If `true`, uses CSS of HTMl element, otherwise no CSS applied * * @since 12.0.0 * @author Pavan Kumar Jadda */ this.useExistingCss = false; /** * A delay in milliseconds to force the print dialog to wait before opened. Default: 0 * * @since 12.0.0 * @author Pavan Kumar Jadda */ this.printDelay = 0; /** * ID of the Mat Paginator * * @since 12.0.0 * @author Pavan Kumar Jadda */ this.paginatorId = ''; /** * HTML tag ID of the Mat-Table Input Filter * * @since 12.0.0 * @author Pavan Kumar Jadda */ this.inputFilterId = ''; /** * If `true`, referenced table is Mat-Table * * @since 12.0.0 * @author Pavan Kumar Jadda */ this.isMatTable = false; /** * If `true` Mat-Table paginator will be hidden * * @since 12.0.0 * @author Pavan Kumar Jadda */ this.hideMatTablePaginator = false; this.printStyleArray = []; this.printOptions = new PrintOptions(); /** * List of Style sheet files * * @since 12.0.0 * @author Pavan Kumar Jadda */ this.styleSheetFileArray = ''; } /** * Prevents the print dialog from opening on the window * * @memberof NgxPrintDirective */ set previewOnly(value) { this.printOptions = { ...this.printOptions, previewOnly: value }; } /** * List of CSS properties that needs to be applied while printing the document * * @since 12.0.0 * @author Pavan Kumar Jadda */ set printStyle({ values }) { for (const key in values) { if (Object.prototype.hasOwnProperty.call(values, key)) { this.printStyleArray.push((key + JSON.stringify(values[key])).replace(/['"]+/g, '')); } } this.returnStyleValues(); } /** * Sets given style sheet files to print document * * @param cssList Comma separated value of CSS file names * * @since 12.0.0 * @author Pavan Kumar Jadda */ set styleSheetFile(cssList) { if (cssList.indexOf(',') !== -1) { const cssFileArray = cssList.split(','); for (const cssFileName of cssFileArray) { this.styleSheetFileArray = this.styleSheetFileArray + NgxPrintDirective.linkTagFn(cssFileName); } } else { this.styleSheetFileArray = NgxPrintDirective.linkTagFn(cssList); } } /** * Build link HTMl tag based on given file name * * @since 12.0.0 * @author Pavan Kumar Jadda */ static linkTagFn(cssFileName) { return `<link rel="stylesheet" type="text/css" href="${cssFileName}">`; } /** * Gets HTML element by tag name * * @since 12.0.0 * @author Pavan Kumar Jadda */ static getElementTag(tag) { const html = []; const elements = document.getElementsByTagName(tag); // @ts-ignore for (const element of elements) { html.push(element.outerHTML); } return html.join('\r\n'); } /** * Print the element upon clicking the button * * @since 12.0.0 * @author Pavan Kumar Jadda */ print() { //Hide paginator for Material table if (this.isMatTable && this.hideMatTablePaginator && this.matTableDataSource) { this.matTableDataSource.paginator = null; } setTimeout(() => { if (this.isMatTable) { this.hideMatPaginatorBeforePrinting(); } // Do something after let printContents; let popupWin; let styles = ''; let links = ''; if (this.useExistingCss) { styles = NgxPrintDirective.getElementTag('style'); links = NgxPrintDirective.getElementTag('link'); } if (this.printSectionId) { printContents = this.document.getElementById(this.printSectionId)?.innerHTML; popupWin = window.open('', '_blank', 'top=0,left=0,height=auto,width=auto'); popupWin?.document.open(); popupWin?.document.write(` <html lang="en-us"> <head> <title>${this.printTitle ? this.printTitle : ''}</title> ${this.returnStyleValues()} ${this.styleSheetFileArray} ${styles} ${links} </head> <body> ${printContents} <script defer> function triggerPrint() { window.removeEventListener('load', triggerPrint, false); setTimeout(() => { window.print(); setTimeout(function() { window.close(); }, 0); }, ${this.printDelay}); } window.addEventListener('load', triggerPrint, false); </script> </body> </html>`); popupWin?.document.close(); //Revert back the mat-paginator after printing if (this.isMatTable) { this.showMatPaginatorAfterPrinting(); } } }, 1000