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
JavaScript
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