UNPKG

ngx-tw

Version:

A comprehensive Angular component library built with Tailwind CSS, providing a modern and customizable set of UI components for Angular applications.

893 lines (879 loc) 256 kB
import * as i0 from '@angular/core'; import { InjectionToken, Inject, Component, Injector, Optional, Injectable, Input, ChangeDetectionStrategy, Self, EventEmitter, forwardRef, Output, TemplateRef, ViewChild, ContentChildren, ContentChild, NgModule, Directive, HostListener } from '@angular/core'; import { BehaviorSubject, debounceTime, tap, filter, switchMap, map, of, isObservable, fromEvent, defer, startWith, merge, take } from 'rxjs'; import { ComponentPortal } from '@angular/cdk/portal'; import { provideAnimations } from '@angular/platform-browser/animations'; import * as i3 from '@angular/common'; import { NgTemplateOutlet, CommonModule, NgClass } from '@angular/common'; import { trigger, state, transition, style, animate } from '@angular/animations'; import * as i1 from '@angular/cdk/overlay'; import { CdkConnectedOverlay } from '@angular/cdk/overlay'; import * as i1$2 from '@angular/forms'; import { FormsModule, FormControl, Validators, ReactiveFormsModule, NG_VALUE_ACCESSOR } from '@angular/forms'; import * as i2 from '@angular/cdk/text-field'; import { TextFieldModule } from '@angular/cdk/text-field'; import * as i1$1 from '@angular/common/http'; import * as i1$3 from '@angular/cdk/dialog'; export { DIALOG_DATA, DialogRef } from '@angular/cdk/dialog'; import { CdkAccordionItem, CdkAccordion } from '@angular/cdk/accordion'; import { CdkMenuTrigger, CdkMenu, CdkMenuItem, CdkMenuItemCheckbox, CdkMenuItemRadio, PARENT_OR_NEW_MENU_STACK_PROVIDER } from '@angular/cdk/menu'; import * as i2$1 from '@angular/cdk/a11y'; import { ActiveDescendantKeyManager } from '@angular/cdk/a11y'; import { DOWN_ARROW, UP_ARROW, ENTER, SPACE, hasModifierKey } from '@angular/cdk/keycodes'; import * as i1$4 from '@angular/router'; import { RouterOutlet, RouterLink } from '@angular/router'; import { ArrayDataSource } from '@angular/cdk/collections'; import * as i2$2 from '@angular/cdk/table'; import { CdkTableModule } from '@angular/cdk/table'; const tw = (template, ...values) => { return template.raw[0] .split(/\s+/) .filter((c) => c.trim()) .join(' '); }; class TwNotificationData { constructor() { this.classes = defaultTwNotificationConfig.classes; } } const defaultTwNotificationConfig = { type: 'success', position: { top: 20, right: 20, }, animation: { fadeOut: 800, fadeIn: 300, }, autoClose: true, autoCloseTimeout: 2500, classes: { container: tw `relative flex justify-around mb-5 w-[382px] max-w-full`, card: tw `max-w-sm w-full bg-white shadow-lg rounded-lg pointer-events-auto ring-1 ring-black ring-opacity-5 overflow-hidden`, success: tw `text-green-400`, info: tw `text-primary-400`, warning: tw `text-yellow-400`, danger: tw `text-danger-400`, svg: tw `w-6 h-6`, button: tw `bg-white rounded-md inline-flex text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500`, }, }; const TW_NOTIFICATION_CONFIG_TOKEN = new InjectionToken('tw-notification-config'); class TwNotificationRef { constructor(overlay) { this.overlay = overlay; } close() { this.overlay.dispose(); } isVisible() { return this.overlay && this.overlay.overlayElement; } getPosition() { return this.overlay.overlayElement.getBoundingClientRect(); } } const twNotificationAnimations = { fadeNotification: trigger('fadeAnimation', [ state('default', style({ opacity: 1 })), transition('void => *', [style({ opacity: 0 }), animate('{{ fadeIn }}ms')]), transition('default => closing', animate('{{ fadeOut }}ms', style({ opacity: 0 }))), ]), }; class NotificationComponent { constructor(data, ref, notificationConfig) { this.data = data; this.ref = ref; this.notificationConfig = notificationConfig; this.animationState = 'default'; // // Extend data with defalt notification config this.data = { ...this.notificationConfig, ...this.data }; } ngOnInit() { // // Set autoclose if (this.data.autoClose === true) this.intervalId = setTimeout(() => (this.animationState = 'closing'), this.data.autoCloseTimeout); } ngOnDestroy() { // // Clear autoclose if (this.data.autoClose === true) clearTimeout(this.intervalId); } close() { this.ref.close(); } onFadeFinished(event) { const { toState } = event; const isFadeOut = toState === 'closing'; const itFinished = this.animationState === 'closing'; if (isFadeOut && itFinished) { this.close(); } } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: NotificationComponent, deps: [{ token: TwNotificationData }, { token: TwNotificationRef }, { token: TW_NOTIFICATION_CONFIG_TOKEN }], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.6", type: NotificationComponent, isStandalone: true, selector: "tw-notification", ngImport: i0, template: "<div\n [class]=\"data.classes?.container\"\n [@fadeAnimation]=\"{value: animationState, params:\n { fadeIn: data.animation?.fadeIn, fadeOut: data.animation?.fadeOut }}\"\n (@fadeAnimation.done)=\"onFadeFinished($event)\"\n>\n <div [class]=\"data.classes?.card\">\n <div class=\"p-4\">\n @if(data.template){\n <ng-container *ngTemplateOutlet=\"data.template; context: { $implicit: data.templateContext }\"></ng-container>\n }@else {\n\n <div class=\"flex items-start\">\n <div class=\"flex-shrink-0\">\n <div [hidden]=\"data.type !== 'success'\">\n <svg\n [class]=\"data.classes?.svg + ' ' + data.classes?.success\"\n xmlns=\"http://www.w3.org/2000/svg\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n aria-hidden=\"true\"\n >\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"2\"\n d=\"M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z\"\n />\n </svg>\n </div>\n\n <div [hidden]=\"data.type !== 'info'\">\n <svg\n [class]=\"data.classes?.svg + ' ' + data.classes?.info\"\n xmlns=\"http://www.w3.org/2000/svg\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"2\"\n d=\"M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z\"\n />\n </svg>\n </div>\n\n <div [hidden]=\"data.type !== 'warning'\">\n <svg\n [class]=\"data.classes?.svg + ' ' + data.classes?.warning\"\n xmlns=\"http://www.w3.org/2000/svg\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"2\"\n d=\"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z\"\n />\n </svg>\n </div>\n\n <div [hidden]=\"data.type !== 'danger'\">\n <svg\n [class]=\"data.classes?.svg + ' ' + data.classes?.danger\"\n xmlns=\"http://www.w3.org/2000/svg\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"2\"\n d=\"M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z\"\n />\n </svg>\n </div>\n </div>\n <div class=\"ml-3 w-0 flex-1 pt-0.5\">\n <p class=\"text-sm font-medium text-gray-900\">\n {{data.title}}\n </p>\n <p\n class=\"mt-1 text-sm text-gray-500\"\n [hidden]=\"!data.text\"\n >\n {{data.text}}\n </p>\n </div>\n <div class=\"ml-4 flex-shrink-0 flex\">\n <button\n [class]=\"data.classes?.button\"\n (click)=\"close()\"\n >\n <span class=\"sr-only\">Close</span>\n <!-- Heroicon name: solid/x -->\n <svg\n class=\"h-5 w-5\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n aria-hidden=\"true\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z\"\n clip-rule=\"evenodd\"\n />\n </svg>\n </button>\n </div>\n </div>\n\n }\n </div>\n </div>\n</div>", dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: CommonModule }], animations: [twNotificationAnimations.fadeNotification] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: NotificationComponent, decorators: [{ type: Component, args: [{ selector: 'tw-notification', imports: [NgTemplateOutlet, CommonModule], animations: [twNotificationAnimations.fadeNotification], template: "<div\n [class]=\"data.classes?.container\"\n [@fadeAnimation]=\"{value: animationState, params:\n { fadeIn: data.animation?.fadeIn, fadeOut: data.animation?.fadeOut }}\"\n (@fadeAnimation.done)=\"onFadeFinished($event)\"\n>\n <div [class]=\"data.classes?.card\">\n <div class=\"p-4\">\n @if(data.template){\n <ng-container *ngTemplateOutlet=\"data.template; context: { $implicit: data.templateContext }\"></ng-container>\n }@else {\n\n <div class=\"flex items-start\">\n <div class=\"flex-shrink-0\">\n <div [hidden]=\"data.type !== 'success'\">\n <svg\n [class]=\"data.classes?.svg + ' ' + data.classes?.success\"\n xmlns=\"http://www.w3.org/2000/svg\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n aria-hidden=\"true\"\n >\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"2\"\n d=\"M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z\"\n />\n </svg>\n </div>\n\n <div [hidden]=\"data.type !== 'info'\">\n <svg\n [class]=\"data.classes?.svg + ' ' + data.classes?.info\"\n xmlns=\"http://www.w3.org/2000/svg\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"2\"\n d=\"M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z\"\n />\n </svg>\n </div>\n\n <div [hidden]=\"data.type !== 'warning'\">\n <svg\n [class]=\"data.classes?.svg + ' ' + data.classes?.warning\"\n xmlns=\"http://www.w3.org/2000/svg\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"2\"\n d=\"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z\"\n />\n </svg>\n </div>\n\n <div [hidden]=\"data.type !== 'danger'\">\n <svg\n [class]=\"data.classes?.svg + ' ' + data.classes?.danger\"\n xmlns=\"http://www.w3.org/2000/svg\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"2\"\n d=\"M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z\"\n />\n </svg>\n </div>\n </div>\n <div class=\"ml-3 w-0 flex-1 pt-0.5\">\n <p class=\"text-sm font-medium text-gray-900\">\n {{data.title}}\n </p>\n <p\n class=\"mt-1 text-sm text-gray-500\"\n [hidden]=\"!data.text\"\n >\n {{data.text}}\n </p>\n </div>\n <div class=\"ml-4 flex-shrink-0 flex\">\n <button\n [class]=\"data.classes?.button\"\n (click)=\"close()\"\n >\n <span class=\"sr-only\">Close</span>\n <!-- Heroicon name: solid/x -->\n <svg\n class=\"h-5 w-5\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n aria-hidden=\"true\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z\"\n clip-rule=\"evenodd\"\n />\n </svg>\n </button>\n </div>\n </div>\n\n }\n </div>\n </div>\n</div>" }] }], ctorParameters: () => [{ type: TwNotificationData }, { type: TwNotificationRef }, { type: undefined, decorators: [{ type: Inject, args: [TW_NOTIFICATION_CONFIG_TOKEN] }] }] }); class TwNotification { constructor(overlay, parentInjector, notificationConfig = defaultTwNotificationConfig) { this.overlay = overlay; this.parentInjector = parentInjector; this.notificationConfig = notificationConfig; this.notificationConfig = notificationConfig || defaultTwNotificationConfig; } show(data) { const positionStrategy = this.getPositionStrategy(); const overlayRef = this.overlay.create({ positionStrategy }); const notificationRef = new TwNotificationRef(overlayRef); this.lastNotification = notificationRef; const injector = this.getInjector(data, notificationRef, this.parentInjector); const notificationPortal = new ComponentPortal(NotificationComponent, null, injector); overlayRef.attach(notificationPortal); return notificationRef; } getPositionStrategy() { return this.overlay .position() .global() .top(this.getPosition()) .right(this.notificationConfig.position?.right + 'px'); } getPosition() { const lastNotificationIsVisible = this.lastNotification && this.lastNotification.isVisible(); const position = lastNotificationIsVisible ? this.lastNotification.getPosition().bottom : this.notificationConfig.position?.top; return position + 'px'; } getInjector(data, notificationRef, parentInjector) { const tokens = new WeakMap(); tokens.set(TwNotificationData, data); tokens.set(TwNotificationRef, notificationRef); return Injector.create({ providers: [ provideAnimations(), { provide: TW_NOTIFICATION_CONFIG_TOKEN, useValue: this.notificationConfig, }, { provide: TwNotificationData, useValue: data }, { provide: TwNotificationRef, useValue: notificationRef }, ], parent: parentInjector, }); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: TwNotification, deps: [{ token: i1.Overlay }, { token: i0.Injector }, { token: TW_NOTIFICATION_CONFIG_TOKEN, optional: true }], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: TwNotification, providedIn: 'root' }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: TwNotification, decorators: [{ type: Injectable, args: [{ providedIn: 'root', }] }], ctorParameters: () => [{ type: i1.Overlay }, { type: i0.Injector }, { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [TW_NOTIFICATION_CONFIG_TOKEN] }] }] }); /** @deprecated This service is deprecated use TwNotification */ class TwAlertService { constructor(_twNotification) { this._twNotification = _twNotification; this._alert$ = new BehaviorSubject(null); } /** * * @deprecated This method is deprecated use TwNotification.show() instead */ info({ title, description = void 0, icon = null, iconColor = null, duration = 3000, showActions = false, primaryActionText = null, secondaryActionText = null, }) { this.notify({ title, type: 'info', description, icon, iconColor, duration: duration, primaryActionText, secondaryActionText, showActions, }); } /** * * @deprecated This method is deprecated use TwNotification.show() instead */ warning({ title, description = void 0, icon = null, iconColor = null, duration = 3000, showActions = false, primaryActionText = null, secondaryActionText = null, }) { this.notify({ title, type: 'warning', description, icon, iconColor, duration: duration, primaryActionText, secondaryActionText, showActions, }); } /** * * @deprecated This method is deprecated use TwNotification.show() instead */ error({ title, description = void 0, icon = null, iconColor = null, duration = 3000, showActions = false, primaryActionText = null, secondaryActionText = null, }) { this.notify({ title, type: 'danger', description, icon, iconColor, duration: duration, primaryActionText, secondaryActionText, showActions, }); } notify({ title, type, description = void 0, icon = null, iconColor = null, duration = 3000, showActions = false, primaryActionText = null, secondaryActionText = null, }) { this._twNotification.show({ title, type, text: description, autoCloseTimeout: duration, }); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: TwAlertService, deps: [{ token: TwNotification }], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: TwAlertService, providedIn: 'root' }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: TwAlertService, decorators: [{ type: Injectable, args: [{ providedIn: 'root', }] }], ctorParameters: () => [{ type: TwNotification }] }); class TwIcon { static { this.ICON_REGISTRY = new Map(); } set size(value) { this._size = value; this._setStyles(); } get size() { return this._size; } set svgIcon(value) { this._svgIcon = value; this._setIcon(); } get svgIcon() { return this._svgIcon; } constructor(_host, _httpClient) { this._host = _host; this._httpClient = _httpClient; this._size = 20; } ngAfterViewInit() { const nativeElement = this._host.nativeElement; if (!nativeElement) return; this._setStyles(); if (!this.svgIcon) this.svgIcon = nativeElement.innerHTML; this._setIcon(); } _setStyles() { const nativeElement = this._host.nativeElement; if (!nativeElement) return; nativeElement.style.setProperty('--c-icon-width', this.size + 'px'); nativeElement.style.setProperty('--c-icon-height', this.size + 'px'); } _setIcon() { const iconName = this.svgIcon; const nativeElement = this._host.nativeElement; if (!nativeElement) return; if (!iconName) throw new Error('tw-icon: invalid icon-name ' + iconName); const [namespace, name] = iconName.split(':'); if (!namespace || !name) throw new Error('tw-icon: Could not determine icon namespace and name from ' + iconName); if (!TwIcon.ICON_REGISTRY.has(iconName)) { this._retriveIcon(nativeElement, iconName, namespace, name); } else { const icon = TwIcon.ICON_REGISTRY.get(iconName); nativeElement.innerHTML = icon; } } ngOnDestroy() { this._retrievalSubscription$?.unsubscribe(); } _retriveIcon(elt, iconName, namespace, name) { this._retrievalSubscription$ = this._httpClient .get(`/assets/icons/${namespace}/${name}.svg`, { responseType: 'text', }) .subscribe((v) => { TwIcon.ICON_REGISTRY.set(iconName, v); elt.innerHTML = v; const svg = elt.firstElementChild; svg.classList.value = ''; svg.style.cssText = `width: ${this.size}px!important; height: ${this.size}px!important;`; }); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: TwIcon, deps: [{ token: i0.ElementRef }, { token: i1$1.HttpClient }], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.0.6", type: TwIcon, isStandalone: true, selector: "tw-icon", inputs: { size: "size", svgIcon: "svgIcon" }, ngImport: i0, template: `<ng-content></ng-content>`, isInline: true, styles: [":host{display:block;width:var(--c-icon-width)!important;height:var(--c-icon-height)!important;min-width:var(--c-icon-width)!important;min-height:var(--c-icon-height)!important}:host svg{width:var(--c-icon-width)!important;height:var(--c-icon-height)!important;min-width:var(--c-icon-width)!important;min-height:var(--c-icon-height)!important}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: TwIcon, decorators: [{ type: Component, args: [{ selector: 'tw-icon', template: `<ng-content></ng-content>`, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:block;width:var(--c-icon-width)!important;height:var(--c-icon-height)!important;min-width:var(--c-icon-width)!important;min-height:var(--c-icon-height)!important}:host svg{width:var(--c-icon-width)!important;height:var(--c-icon-height)!important;min-width:var(--c-icon-width)!important;min-height:var(--c-icon-height)!important}\n"] }] }], ctorParameters: () => [{ type: i0.ElementRef }, { type: i1$1.HttpClient }], propDecorators: { size: [{ type: Input }], svgIcon: [{ type: Input }] } }); class TwInputField { get errors() { return this._ngControl?.dirty && this._ngControl?.errors ? Object.keys(this._ngControl.errors) .filter((e) => e !== 'required') .join('.\n') : null; } constructor(_ngControl) { this._ngControl = _ngControl; this.name = ''; this.label = ''; this.value = ''; this.required = false; this.pattern = ''; this.placeholder = ''; this.disabled = false; this.inputType = 'text'; this.showLabel = true; this.multiline = false; this._onChangeFns = []; this._onTouchedFns = []; if (this._ngControl) { this._ngControl.valueAccessor = this; } } writeValue(obj) { this.value = obj || ''; } registerOnChange(fn) { if (fn) this._onChangeFns.push(fn); } registerOnTouched(fn) { if (fn) this._onTouchedFns.push(fn); } setDisabledState(isDisabled) { this.disabled = isDisabled; } ngOnInit() { } onChange(event) { const value = event.target.value; this._onChangeFns?.forEach((fn) => fn(value)); this._onTouchedFns?.forEach((fn) => fn(value)); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: TwInputField, deps: [{ token: i1$2.NgControl, optional: true, self: true }], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.6", type: TwInputField, isStandalone: true, selector: "tw-input-field", inputs: { iconSuffix: "iconSuffix", iconSuffixClass: "iconSuffixClass", iconPrefix: "iconPrefix", iconPrefixClass: "iconPrefixClass", twClass: "twClass", name: "name", label: "label", value: "value", maxLength: "maxLength", minLength: "minLength", required: "required", pattern: "pattern", placeholder: "placeholder", disabled: "disabled", inputType: "inputType", color: "color", showLabel: "showLabel", multiline: "multiline" }, host: { classAttribute: "tw-input-wrapper" }, providers: [], ngImport: i0, template: "<div class=\"flex flex-col gap-2\">\n @if (showLabel) {\n <label\n [for]=\"name\"\n class=\"font-semibold\"\n >{{label || placeholder}}</label>\n }\n <div class=\"relative flex items-center w-full\">\n @if (iconPrefix) {\n <span\n class=\"absolute ml-2\"\n >\n <tw-icon\n class=\"text-gray-400 {{ iconPrefixClass }}\"\n [svgIcon]=\"iconPrefix\"\n />\n </span>\n }\n\n @if (multiline) {\n <textarea\n cdkTextareaAutosize\n cdkAutosizeMinRows=\"3\"\n cdkAutosizeMaxRows=\"7\"\n [title]=\"placeholder\"\n [placeholder]=\"placeholder\"\n [(ngModel)]=\"value\"\n [disabled]=\"disabled\"\n [attr.maxLength]=\"maxLength\"\n [attr.minLength]=\"minLength\"\n [required]=\"required\"\n [name]=\"name\"\n [pattern]=\"pattern\"\n (input)=\"onChange($event)\"\n class=\"tw-input-field w-full {{ color }} {{twClass}}\"\n [ngClass]=\"{\n 'icon-left': iconPrefix && !iconSuffix,\n 'icon-right': iconSuffix && !iconPrefix,\n 'icon-left-right': iconSuffix && iconPrefix\n }\"\n ></textarea>\n }@else {\n <input\n [title]=\"placeholder\"\n [type]=\"inputType\"\n [placeholder]=\"placeholder\"\n [value]=\"value\"\n [disabled]=\"disabled\"\n [attr.maxLength]=\"maxLength\"\n [attr.minLength]=\"minLength\"\n [required]=\"required\"\n [name]=\"name\"\n [pattern]=\"pattern\"\n (input)=\"onChange($event)\"\n class=\"tw-input-field w-full {{ color }} {{twClass}}\"\n [ngClass]=\"{\n 'icon-left': iconPrefix && !iconSuffix,\n 'icon-right': iconSuffix && !iconPrefix,\n 'icon-left-right': iconSuffix && iconPrefix\n }\"\n />\n\n }\n\n\n @if (iconSuffix) {\n <span\n class=\"absolute mr-2 right-0\"\n >\n <tw-icon\n class=\"text-gray-400 {{ iconSuffixClass }}\"\n [svgIcon]=\"iconSuffix\"\n />\n </span>\n }\n </div>\n @if (errors) {\n <span class=\"bg-red-100 text-red-900 rounded-lg shadow-sm px-2 py-1 text-sm\">{{errors}}</span>\n }\n</div>", dependencies: [{ kind: "component", type: TwIcon, selector: "tw-icon", inputs: ["size", "svgIcon"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1$2.PatternValidator, selector: "[pattern][formControlName],[pattern][formControl],[pattern][ngModel]", inputs: ["pattern"] }, { kind: "directive", type: i1$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: TextFieldModule }, { kind: "directive", type: i2.CdkTextareaAutosize, selector: "textarea[cdkTextareaAutosize]", inputs: ["cdkAutosizeMinRows", "cdkAutosizeMaxRows", "cdkTextareaAutosize", "placeholder"], exportAs: ["cdkTextareaAutosize"] }] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: TwInputField, decorators: [{ type: Component, args: [{ imports: [TwIcon, NgClass, FormsModule, TextFieldModule], selector: 'tw-input-field', host: { class: 'tw-input-wrapper', }, providers: [], template: "<div class=\"flex flex-col gap-2\">\n @if (showLabel) {\n <label\n [for]=\"name\"\n class=\"font-semibold\"\n >{{label || placeholder}}</label>\n }\n <div class=\"relative flex items-center w-full\">\n @if (iconPrefix) {\n <span\n class=\"absolute ml-2\"\n >\n <tw-icon\n class=\"text-gray-400 {{ iconPrefixClass }}\"\n [svgIcon]=\"iconPrefix\"\n />\n </span>\n }\n\n @if (multiline) {\n <textarea\n cdkTextareaAutosize\n cdkAutosizeMinRows=\"3\"\n cdkAutosizeMaxRows=\"7\"\n [title]=\"placeholder\"\n [placeholder]=\"placeholder\"\n [(ngModel)]=\"value\"\n [disabled]=\"disabled\"\n [attr.maxLength]=\"maxLength\"\n [attr.minLength]=\"minLength\"\n [required]=\"required\"\n [name]=\"name\"\n [pattern]=\"pattern\"\n (input)=\"onChange($event)\"\n class=\"tw-input-field w-full {{ color }} {{twClass}}\"\n [ngClass]=\"{\n 'icon-left': iconPrefix && !iconSuffix,\n 'icon-right': iconSuffix && !iconPrefix,\n 'icon-left-right': iconSuffix && iconPrefix\n }\"\n ></textarea>\n }@else {\n <input\n [title]=\"placeholder\"\n [type]=\"inputType\"\n [placeholder]=\"placeholder\"\n [value]=\"value\"\n [disabled]=\"disabled\"\n [attr.maxLength]=\"maxLength\"\n [attr.minLength]=\"minLength\"\n [required]=\"required\"\n [name]=\"name\"\n [pattern]=\"pattern\"\n (input)=\"onChange($event)\"\n class=\"tw-input-field w-full {{ color }} {{twClass}}\"\n [ngClass]=\"{\n 'icon-left': iconPrefix && !iconSuffix,\n 'icon-right': iconSuffix && !iconPrefix,\n 'icon-left-right': iconSuffix && iconPrefix\n }\"\n />\n\n }\n\n\n @if (iconSuffix) {\n <span\n class=\"absolute mr-2 right-0\"\n >\n <tw-icon\n class=\"text-gray-400 {{ iconSuffixClass }}\"\n [svgIcon]=\"iconSuffix\"\n />\n </span>\n }\n </div>\n @if (errors) {\n <span class=\"bg-red-100 text-red-900 rounded-lg shadow-sm px-2 py-1 text-sm\">{{errors}}</span>\n }\n</div>" }] }], ctorParameters: () => [{ type: i1$2.NgControl, decorators: [{ type: Self }, { type: Optional }] }], propDecorators: { iconSuffix: [{ type: Input }], iconSuffixClass: [{ type: Input }], iconPrefix: [{ type: Input }], iconPrefixClass: [{ type: Input }], twClass: [{ type: Input }], name: [{ type: Input }], label: [{ type: Input }], value: [{ type: Input }], maxLength: [{ type: Input }], minLength: [{ type: Input }], required: [{ type: Input }], pattern: [{ type: Input }], placeholder: [{ type: Input }], disabled: [{ type: Input }], inputType: [{ type: Input }], color: [{ type: Input }], showLabel: [{ type: Input }], multiline: [{ type: Input }] } }); class TwSpinner { constructor() { this.color = ''; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: TwSpinner, deps: [], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.0.6", type: TwSpinner, isStandalone: true, selector: "tw-spinner", inputs: { color: "color" }, ngImport: i0, template: ` <svg class="animate-spin -ml-1 mr-3 h-5 w-5 {{ color }}" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" > <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" ></circle> <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" ></path> </svg> `, isInline: true }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: TwSpinner, decorators: [{ type: Component, args: [{ selector: 'tw-spinner', standalone: true, template: ` <svg class="animate-spin -ml-1 mr-3 h-5 w-5 {{ color }}" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" > <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" ></circle> <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" ></path> </svg> `, }] }], propDecorators: { color: [{ type: Input }] } }); const HTML_EVENTS = [ 'abort', 'afterprint', 'animationend', 'animationiteration', 'animationstart', 'beforeprint', 'beforeunload', 'blur', 'canplay', 'canplaythrough', 'change', 'click', 'contextmenu', 'copy', 'cut', 'dblclick', 'DOMContentLoaded', 'drag', 'dragend', 'dragenter', 'dragleave', 'dragover', 'dragstart', 'drop', 'durationchange', 'ended', 'error', 'focus', 'focusin', 'focusout', 'fullscreenchange', 'fullscreenerror', 'hashchange', 'input', 'invalid', 'keydown', 'keypress', 'keyup', 'load', 'loadeddata', 'loadedmetadata', 'loadstart', 'message', 'mousedown', 'mouseenter', 'mouseleave', 'mousemove', 'mouseover', 'mouseout', 'mouseup', 'offline', 'online', 'open', 'pagehide', 'pageshow', 'paste', 'pause', 'play', 'playing', 'popstate', 'progress', 'ratechange', 'resize', 'reset', 'scroll', 'search', 'seeked', 'seeking', 'select', 'show', 'stalled', 'storage', 'submit', 'suspend', 'timeupdate', 'toggle', 'touchcancel', 'touchend', 'touchmove', 'touchstart', 'transitionend', 'unload', 'volumechange', 'waiting', 'wheel', ]; class TwElement { bindEvents(element, component) { HTML_EVENTS.forEach((ev) => { if (component[ev] && component[ev] instanceof EventEmitter) { element.addEventListener(ev, (event) => component[ev].emit(event)); } }); } } const OverlayPositions = [ { originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 8, }, { originX: 'end', originY: 'bottom', overlayX: 'end', overlayY: 'top', offsetY: 8, }, { originX: 'start', originY: 'top', overlayX: 'start', overlayY: 'bottom', offsetY: -8, }, { originX: 'end', originY: 'top', overlayX: 'end', overlayY: 'bottom', offsetY: -8, }, ]; function isFunction(value) { return typeof value === 'function'; } class AutoCompleteManager { set suggestions(value) { this._suggestions = value; } get suggestions() { return this._suggestions; } constructor(valueChangeObservable) { this.valueChangeObservable = valueChangeObservable; this.filteredSuggestions = []; this.filterFn = (value, item) => this.getDisplayText(item).toLowerCase().includes(value.toLowerCase()); this.displayFactory = void 0; this.isOpen = false; this.disabled = false; this.suggestionsLoading = false; this._suggestions = []; } init() { this.valueChangeObservable .pipe(debounceTime(300), tap((v) => (v?.length === 0 ? this.closeDropdown() : void 0)), filter((value) => !!value && value.length >= 1), tap(() => ((this.suggestionsLoading = true), this.openDropdown())), switchMap((value) => this.filterSuggestions(value))) .subscribe((filtered) => { this.suggestionsLoading = false; this.filteredSuggestions = filtered; }); } filterSuggestions(value) { const filterObs = (obs) => obs.pipe(map((suggestions) => suggestions.filter((s) => this.filterFn(value, s)))); const resolveObs = (fn) => { const result = fn(value); return isObservable(result) ? result : of(result); }; const suggestionsArray$ = isObservable(this._suggestions) ? filterObs(this._suggestions) : isFunction(this._suggestions) ? resolveObs(this._suggestions) : of(this._suggestions.filter((s) => this.filterFn(value, s))); return suggestionsArray$; } getDisplayText(item) { if (this.displayFactory) { return this.displayFactory(item).text; } return typeof item === 'string' ? item : JSON.stringify(item); } openDropdown() { if (this.disabled === true) return; this.isOpen = true; } closeDropdown() { this.isOpen = false; } selectSuggestion(suggestion) { if (!this.filteredSuggestions.find((item) => item === suggestion)) { return false; } this.closeDropdown(); return true; } } class TwAutocomplete { set maxLength(value) { this._maxLength = value; if (this.searchControl) { this.updateValidators(); } } get maxLength() { return this._maxLength; } set minLength(value) { this._minLength = value; if (this.searchControl) { this.updateValidators(); } } get minLength() { return this._minLength; } set required(value) { this._required = value; if (this.searchControl) { this.updateValidators(); } } get required() { return this._required; } set pattern(value) { this._pattern = value; if (this.searchControl) { this.updateValidators(); } } get pattern() { return this._pattern; } set disabled(value) { if (value) this.searchControl.disable(); else this.searchControl.enable(); } get disabled() { return this.searchControl.disabled; } get value() { return this._value; } set value(val) { this._value = val; this.searchControl.setValue(this.autoCompleteManager.getDisplayText(val), { emitEvent: false, }); } set suggestions(value) { this.autoCompleteManager.suggestions = value; } get suggestions() { return this.autoCompleteManager.suggestions; } get suggestionsLoading() { return this.autoCompleteManager.suggestionsLoading; } set displayFactory(value) { this.autoCompleteManager.displayFactory = value; } get displayFactory() { return this.autoCompleteManager.displayFactory; } set filterFn(fn) { this.autoCompleteManager.filterFn = fn; } get filterFn() { return this.autoCompleteManager.filterFn; } get minFieldSize() { return this._minFieldSize; } get filteredSuggestions() { return this.autoCompleteManager.filteredSuggestions; } constructor(elementRef, overlay, cdr) { this.elementRef = elementRef; this.overlay = overlay; this.cdr = cdr; this.name = ''; this.label = ''; this._required = false; this._pattern = ''; this.placeholder = ''; this.inputType = 'text'; this.showLabel = true; this.multiline = false; this._minFieldSize = 0; this.optionTemplate = null; this.selectionChanged = new EventEmitter(); this.searchControl = new FormControl(''); this.positions = OverlayPositions; this.onChange = []; this.onTouched = []; this.autoCompleteManager = new AutoCompleteManager(this.searchControl.valueChanges); this.blockScrollStrategy = this.overlay.scrollStrategies.block(); } ngOnInit() { this.updateValidators(); } ngAfterViewInit() { // Initialize the offset width this._minFieldSize = this.elementRef.nativeElement.offsetWidth; // Set up ResizeObserver to watch for size changes if (typeof ResizeObserver !== 'undefined') { this.resizeObserver = new ResizeObserver((entries) => { for (const entry of entries) { this._minFieldSize = entry.contentRect.width; this.cdr.detectChanges(); } }); this.resizeObserver.observe(this.elementRef.nativeElement); } this.autoCompleteManager.init(); } ngOnDestroy() { if (this.resizeObserver) { this.resizeObserver.disconnect(); } } updateValidators() { const validators = []; if (this.required) { validators.push(Validators.required); } if (this.minLength !== undefined) { validators.push(Validators.minLength(this.minLength)); } if (this.maxLength !== undefined) { validators.push(Validators.maxLength(this.maxLength)); } if (this.pattern) { validators.push(Validators.pattern(this.pattern)); } this.searchControl.setValidators(validators); this.searchControl.updateValueAndValidity({ emitEvent: false, }); } validate(control) { if (!this.searchControl) { return null; } // If the component is disabled, it's valid if (this.disabled) { return null; } // Return the validation errors from the internal search control return this.searchControl.errors; } selectSuggestion(suggestion) { const value = this.autoCompleteManager.getDisplayText(suggestion); this.searchControl.setValue(value, { emitEvent: false, }); this.propagateOnchange(suggestion); this.autoCompleteManager.closeDropdown(); } writeValue(value) { this._value = value; this.searchControl.setValue(this.autoCompleteManager.getDisplayText(value), { emitEvent: false, }); } propagateOnchange(value) { this.onChange.forEach((fn) => fn(value)); this.selectionChanged.emit(value); } registerOnChange(fn) { this.onChange.push(fn); } registerOnTouched(fn) { this.onTouched.push(fn); } setDisabledState(isDisabled) { this.disabled = isDisabled; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: TwAutocomplete, deps: [{ token: i0.ElementRef }, { token: i1.Overlay }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.6", type: TwAutocomplete, isStandalone: true, selector: "tw-autocomplete", inputs: { iconSuffix: "iconSuffix", iconSuffixClass: "iconSuffixClass", iconPrefix: "iconPrefix", iconPrefixClass: "iconPrefixClass", twClass: "twClass", name: "name", label: "label", maxLength: "maxLength", minLength: "minLength", required: "required", pattern: "pattern", placeholder: "placeholder", disabled: "disabled", inputType: "inputType", color: "color", showLabel: "showLabel", value: "value", suggestions: "suggestions", displayFactory: "displayFactory", optionTemplate: "optionTemplate", filterFn: "filterFn" }, outputs: { selectionChanged: "selectionChanged" }, providers: [ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => TwAutocomplete), multi: true, }, ], ngImport: i0, template: "<div class=\"relative w-full max-w-sm\">\n <tw-input-field\n [formControl]=\"searchControl\"\n (focus)=\"autoCompleteManager.isOpen = filteredSuggestions.length > 0\"\n [iconSuffix]=\"iconSuffix\"\n [iconSuffixClass]=\"iconSuffixClass\"\n [iconPrefix]=\"iconPrefix\"\n [iconPrefixClass]=\"iconPrefixClass\"\n [twClass]=\"twClass\"\n [name]=\"name\"\n [label]=\"label\"\n [maxLength]=\"maxLength\"\n [minLength]=\"minLength\"\n [required]=\"required\"\n [pattern]=\"pattern\"\n [placeholder]=\"placeholder\"\n [inputType]=\"inputType\"\n [color]=\"color\"\n [showLabel]=\"showLabel\"\n [multiline]=\"multiline\"\n />\n <!-- Dropdown Suggestions -->\n <ng-template\n cdkConnectedOverlay\n [cdkConnectedOverlayHasBackdrop]=\"true\"\n [cdkConnectedOverlayBackdropClass]=\"'cdk-overlay-transparent-backdrop'\"\n [cdkConnectedOverlayOrigin]=\"elementRef.nativeElement.firstElementChild\"\n [cdkConnectedOverlayOpen]=\"autoCompleteManager.isOpen\"\n [cdkConnectedOverlayPositions]=\"positions\"\n cdkConnectedOverlayMinWidth=\"100px\"\n [cdkConnectedOverlayScrollStrategy]=\"blockScrollStrategy\"\n (backdropClick)=\"autoCompleteManager.closeDropdown()\"\n (detach)=\"autoCompleteManager.closeDropdown()\"\n >\n <div class=\"w-full mt-1 bg-white border border-gray-200 rounded-lg shadow-lg max-h-60 overflow-y-auto\">\n @if(!suggestionsLoading){\n @if (filteredSuggestions.length > 0) {\n <ul>\n @for (suggestion of filteredSuggestions; track $index) {\n <li\n (click)=\"selectSuggestion(suggestion)\"\n class=\"p-2 tw-option\"\n >\n @if (optionTemplate) {\n <ng-container *ngTemplateOutlet=\"optionTemplate; context: { $implicit: suggestion }\"></ng-container>\n\n }@else {\n <ng-container>{{ autoCompleteManager.getDisplayText(suggestion) }}</ng-container>\n }\n </li>\n }\n </ul>\n }\n @else{\n <div class=\"p-2 text-gray-500\">\n No suggestions found.\n </div>\n }\n }@else {\n <div class=\"flex justify-center p-4\">\n <tw-spinner></tw-spinner>\n </div>\n }\n </div>\n </ng-template>\n</div>", dependencies: [{ kind: "ngmodule", type: Reacti