UNPKG

@controladad/ng-base

Version:
909 lines (897 loc) 561 kB
import * as i0 from '@angular/core'; import { InjectionToken, inject, Injectable, signal, computed, EventEmitter, Component, ViewChild, Input, Output, HostBinding, ElementRef, ChangeDetectionStrategy, HostListener, effect, DestroyRef, input, ContentChildren, Directive, PLATFORM_ID, Inject, Optional, Self, Host, TemplateRef, output, NgModule, Injector, runInInjectionContext, ViewChildren, Pipe, ChangeDetectorRef, provideAppInitializer, importProvidersFrom } from '@angular/core'; import { DomSanitizer } from '@angular/platform-browser'; import * as i4 from '@angular/material/icon'; import { MatIconRegistry, MatIconModule, MatIcon } from '@angular/material/icon'; import { createStore, withProps, select, getStore, enableElfProdMode } from '@ngneat/elf'; import { toSignal, takeUntilDestroyed, toObservable } from '@angular/core/rxjs-interop'; import { persistState, localStorageStrategy, sessionStorageStrategy } from '@ngneat/elf-persist-state'; import { Subject, debounceTime, startWith, BehaviorSubject, pipe, tap, Subscription, of, map, merge, distinctUntilChanged, debounce, timer, Observable, fromEvent, take, combineLatest, delay, switchMap, filter, finalize, catchError, NEVER, retry, throwError, interval } from 'rxjs'; import { RouterLink, Router, ResolveStart, NavigationEnd } from '@angular/router'; import * as jalali from 'date-fns-jalali'; import { addDays, addMonths, addYears, toDate, getMonth, parseISO, format, getDate, getDay, setDay, setMonth, getDaysInMonth, getYear, parse, set } from 'date-fns-jalali'; import * as dateFns from 'date-fns'; import * as i1$b from '@angular/material/dialog'; import { MatDialogRef, MAT_DIALOG_DATA, MatDialog, MatDialogModule } from '@angular/material/dialog'; import * as i1$2 from '@angular/forms'; import { ReactiveFormsModule, Validators, FormGroup, FormControl, UntypedFormControl, FormsModule } from '@angular/forms'; import * as i1 from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button'; import * as i2 from '@angular/material/progress-spinner'; import { MatProgressSpinnerModule, MatProgressSpinner } from '@angular/material/progress-spinner'; import * as i1$6 from '@angular/common'; import { NgStyle, NgTemplateOutlet, AsyncPipe, isPlatformServer, DatePipe, NgClass, DecimalPipe, formatNumber, Location, registerLocaleData } from '@angular/common'; import * as i3 from '@angular/material/tooltip'; import { MatTooltipModule } from '@angular/material/tooltip'; import { switchMap as switchMap$1 } from 'rxjs/operators'; import * as i1$1 from '@angular/material/checkbox'; import { MatCheckboxModule } from '@angular/material/checkbox'; import { formControl, isFormControlExtended, formGroup, Validators as Validators$1, provideFormErrors } from '@al00x/forms'; import { trigger, transition, style, animate } from '@angular/animations'; import * as i1$4 from '@angular/material/menu'; import { MatMenuModule, MatMenuTrigger } from '@angular/material/menu'; import * as i6 from '@angular/material/datepicker'; import { MatDatepickerModule, DateRange, MAT_DATE_RANGE_SELECTION_STRATEGY, DefaultMatCalendarRangeStrategy } from '@angular/material/datepicker'; import * as i2$1 from '@angular/material/form-field'; import { MatFormFieldModule } from '@angular/material/form-field'; import * as i1$5 from '@angular/material/input'; import { MatInputModule } from '@angular/material/input'; import { MatChipsModule } from '@angular/material/chips'; import * as i8 from '@angular/material/progress-bar'; import { MatProgressBarModule } from '@angular/material/progress-bar'; import * as i1$3 from '@angular/material/radio'; import { MatRadioButton, MatRadioModule } from '@angular/material/radio'; import tippy from 'tippy.js'; import Inputmask from 'inputmask'; import { MatButtonToggleModule } from '@angular/material/button-toggle'; import * as i9 from 'ngx-quill'; import { QuillModule } from 'ngx-quill'; import * as i3$1 from '@angular/cdk/bidi'; import * as i1$7 from '@al00x/screen-detector'; import { ScreenDetectorService } from '@al00x/screen-detector'; import * as i1$8 from '@angular/material/slider'; import { MatSliderModule } from '@angular/material/slider'; import * as i1$9 from '@angular/material/slide-toggle'; import { MatSlideToggleModule } from '@angular/material/slide-toggle'; import { MatTabGroup, MatTab, MatTabLabel } from '@angular/material/tabs'; import * as i1$a from 'ngx-skeleton-loader'; import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; import * as i1$c from '@al00x/printer'; import { AlxPrintModule } from '@al00x/printer'; import * as i1$d from '@angular/material/snack-bar'; import { MAT_SNACK_BAR_DATA, MatSnackBarModule } from '@angular/material/snack-bar'; import * as i1$e from '@angular/material/table'; import { MatColumnDef, MatCellDef, MatHeaderCellDef, MatFooterCellDef, MatCell, MatHeaderCell, MatTableModule } from '@angular/material/table'; import { MatBadge, MatBadgeModule } from '@angular/material/badge'; import * as i2$2 from '@angular/cdk/a11y'; import { A11yModule } from '@angular/cdk/a11y'; import { HttpClient, HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http'; import * as i1$f from 'ngx-file-drop'; import { NgxFileDropModule } from 'ngx-file-drop'; import { DateAdapter, MAT_DATE_LOCALE, MAT_DATE_FORMATS } from '@angular/material/core'; import { faIR } from 'date-fns-jalali/locale'; import { provideAnimations } from '@angular/platform-browser/animations'; import { DateFnsAdapter } from '@angular/material-date-fns-adapter'; import { loadTranslations } from '@angular/localize'; import { enUS } from 'date-fns/locale'; import localeEn from '@angular/common/locales/en'; const API_BASEURL = new InjectionToken('API_BASEURL'); const ENVIRONMENT = new InjectionToken('ENVIRONMENT'); // prettier-ignore const baseIcons = [ 'add', 'arrow-down', 'arrow-up', 'arrow-left', 'arrow-right', 'calendar', 'camera', 'check', 'check-double', 'chevron-down', 'chevron-left', 'chevron-right', 'chevron-up', 'clock', 'close', 'delete', 'dropdown', 'edit', 'edit-box', 'error', 'excel-file', 'eye', 'eye-slash', 'filter', 'filter-filled', 'hashtag', 'info', 'info-circle', 'list', 'location', 'location-check', 'login', 'logout', 'menu', 'nav', 'numeric-down', 'numeric-up', 'paper', 'paper-details', 'password', 'phone', 'plate', 'play', 'plus', 'power', 'print', 'question-circle', 'refresh', 'reports', 'save', 'search', 'settings', 'sort', 'sort-down', 'sort-up', 'time', 'trash', 'trash-alt', 'user', 'user-circle', 'users', 'wrench' ]; let isBaseRegistered = false; function registerIcons(config) { const sanitizer = inject(DomSanitizer); const iconRegistry = inject(MatIconRegistry); if (!isBaseRegistered) { for (const icon of baseIcons) { iconRegistry.addSvgIcon(icon, sanitizer.bypassSecurityTrustResourceUrl(`./assets/base/icons/${icon}.svg`)); } isBaseRegistered = true; } for (const icon of config?.additionalIcons ?? []) { let dir = config?.additionalIconsDir ?? `./assets/icons`; if (dir.endsWith('/')) { dir = dir.slice(0, -1); } iconRegistry.addSvgIcon(icon, sanitizer.bypassSecurityTrustResourceUrl(`${dir}/${icon}.svg`)); } } class CacGlobalConfig { // Do not change this variable manually, it's automatically updated via the provider. static { this.defaultLang = 'en'; } static { this.applicationName = ''; } static { this.localization = { langs: ['en'], }; } // applies application name to store keys, for example if your store key is `auth`, it will become `app_name_auth` static { this.applyPrefixToStorageKeys = true; } static generateStoreKey(key) { return `${this.applyPrefixToStorageKeys && this.applicationName.length ? `${this.applicationName}_` : ''}${key}`; } } class ApiInterceptor { constructor() { this.apiBaseUrl = inject(API_BASEURL); } // using inject() causes circular error, idk why... // readonly apiBaseUrl = inject(API_BASEURL); intercept(request, next) { request = request.clone({ url: request.url.startsWith('/') ? this.apiBaseUrl + request.url : request.url, }); return next.handle(request); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: ApiInterceptor, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: ApiInterceptor }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: ApiInterceptor, decorators: [{ type: Injectable }] }); class BaseStore { constructor(storeOpts) { this.storeOpts = storeOpts; const key = storeOpts.exactKey ? storeOpts.key : CacGlobalConfig.generateStoreKey(storeOpts.key); const store = createStore({ name: key }, withProps(storeOpts.default ?? {})); persistState(store, { key: key, storage: storeOpts.storageStrategy ?? localStorageStrategy, }); this.store = store; this.state$ = this.store.pipe(); try { this.signal = toSignal(this.state$); } catch { /* empty */ } } get state() { return this.get(); } get() { return this.store.getValue(); } patch(value) { return this.store.update((s) => ({ ...s, ...value, })); } } class _AppBaseStore extends BaseStore { constructor(defaults) { super({ key: 'app', default: defaults, }); this.rememberMe$ = this.store.pipe(select(this.rememberMe)); this.lang$ = this.store.pipe(select(this.lang)); } rememberMe() { return this.get().rememberMe ?? false; } setRememberMe(value) { // @ts-ignore this.patch({ rememberMe: value, }); } lang() { return this.get().lang ?? CacGlobalConfig.defaultLang; } setLang(value) { // @ts-ignore this.patch({ lang: value, }); } } // This is dummy, used to make service out of the `_AppBaseStore`. For extension, `_AppBaseStore` should be used class AppBaseStore extends _AppBaseStore { constructor() { super(); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: AppBaseStore, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: AppBaseStore, providedIn: 'root' }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: AppBaseStore, decorators: [{ type: Injectable, args: [{ providedIn: 'root', }] }], ctorParameters: () => [] }); class SortModel { constructor(key, direction) { this._key = signal(undefined); this._direction = signal('desc'); this._changes$ = new Subject(); this.key = this._key.asReadonly(); this.direction = this._direction.asReadonly(); this.changes$ = this._changes$.pipe(debounceTime(50)); this._key.set(key); this._direction.set(direction ?? 'desc'); } setKey(sort) { this._key.set(sort); this.emit(); } setDirection(direction) { this._direction.set(direction); this.emit(); } create() { const key = this.key(); return key ? { key: key, direction: this.direction(), } : undefined; } emit() { this._changes$.next([this._key(), this._direction()]); } } // TODO: Update constructor initial parameters (turn into object, it's messy currently) // TODO: add formControl binding // TODO: turn off multiple mode by default class SelectionModel { constructor(itemsCount, multiple, initial, itemToId) { this._totalCount = 0; this._selectedCount = 0; this._currentViewItems = []; this._blockBoundUpdate = false; this.indeterminate = signal(false); this.allSelected = signal(false); this.selected = signal([]); this.selectedIds = signal({}); this.selectedCount = computed(() => this.selected()?.length ?? 0); this.hasSelection = computed(() => this.selectedCount() !== 0); this.id = crypto.randomUUID(); this._totalCount = itemsCount ?? 0; this._multiple = multiple ?? true; this._itemToId = itemToId ?? ((t) => (t && typeof t === 'object' && 'id' in t ? t.id : t)); initial ? this.select(...initial) : null; } get isMultiple() { return this._multiple; } select(...items) { if (this._multiple) { this.set(...[...new Set([...this.selected(), ...items])]); } else if (items.length) { // this.clear(); this.set(items[0]); } } deselect(...items) { const itemsId = items.map(this._itemToId); this.set(...this.selected().filter((t) => !itemsId.includes(this._itemToId(t)))); } toggle(...items) { const selected = this.selected(); if (this._multiple) { let newItems = [...selected]; if (newItems.length > 0) { for (const item of items) { const index = selected.findIndex((t) => this._itemToId(t) === this._itemToId(item)); if (index !== -1) { newItems.splice(index, 1); } else { newItems.push(item); } } } else { newItems = [...items]; } this.set(...newItems); } else { if (selected.some(t => t === items[0])) { this.clear(); } else { this.set(items[0]); } } } selectAll() { this.select(...this._currentViewItems); } deselectAll() { this.deselect(...this._currentViewItems); } toggleAll() { if (this._selectedCount === this._totalCount) { this.deselectAll(); } else { this.selectAll(); } } clear() { if (this._selectedCount === 0) return; this.set(); } set(...items) { this.selected.set(items); this.selectedIds.set(items .map((t) => this._itemToId(t)) .reduce((pre, cur) => ({ ...pre, [cur]: true, }), {})); this._selectedCount = items.length; this.calculateSelectionState(); this.updateBoundedFormControl(); } isSelected(item) { return !!this.selectedIds()[this._itemToId(item)]; } setTotalCount(count) { this._totalCount = count; this.calculateSelectionState(); } setItems(items, setTotalCount = true) { this._currentViewItems = items; if (setTotalCount) { this._totalCount = items.length; } this.calculateSelectionState(); } setItemToIdFn(fn) { this._itemToId = fn; } setMultiple(value) { this._multiple = value === undefined ? true : value; } bindFormControl(control, destroyRef) { this._boundFormControl = control; if (!control) return; control.valueChanges.pipe(startWith(control.value), takeUntilDestroyed(destroyRef)).subscribe((v) => { if (this._blockBoundUpdate || v === this.selectedIds()) { this._blockBoundUpdate = false; return; } if (v === null || v === undefined) { this.clear(); } else { this.set(...(v instanceof Array ? v : [v])); } }); } updateBoundedFormControl() { if (!this._boundFormControl) return; this._blockBoundUpdate = true; this._boundFormControl.setValue(this.selectedIds()); this._boundFormControl.setSelectedItems(this.selected()); } calculateSelectionState() { const selectedViewItemsCount = this._currentViewItems .map(this._itemToId) .filter((t) => this.selectedIds()[t]).length; if (this._selectedCount === 0 || selectedViewItemsCount === 0) { this.indeterminate.set(false); this.allSelected.set(false); } else if (this._selectedCount === this._totalCount) { this.indeterminate.set(false); this.allSelected.set(true); } else { this.indeterminate.set(selectedViewItemsCount !== this._currentViewItems.length); this.allSelected.set(!this.indeterminate()); } } } const ActiveValues = [ { value: true, label: 'Active' }, { value: false, label: 'Inactive' }, ]; const SuspendedValues = [ { value: false, label: 'Active' }, { value: true, label: 'Inactive' }, ]; const BooleanValues = [ { value: true, label: 'Yes' }, { value: false, label: 'No' }, ]; class FilterModel { constructor(init, overrideEmpty) { this.overrideEmpty = overrideEmpty; this._filters = signal({}); this._changes$ = new Subject(); this.filters = computed(() => { const obj = this._filters(); if (!obj) return this.overrideEmpty; Object.keys(obj).forEach((key) => obj[key] === undefined && delete obj[key]); if (Object.keys(obj).length === 0) return this.overrideEmpty; return obj; }); this.filtersArray = computed(() => { const filters = this.filters(); if (!filters) return []; return Object.values(filters); }); this.hasFilter = computed(() => { return this.filters() !== undefined; }); this.changes$ = this._changes$.pipe(debounceTime(50)); this._filters.set(init ?? overrideEmpty ?? {}); } set(data, values, emit = true) { if (values === undefined || values.every((t) => t.value === undefined || t.value === null)) { this.remove(data.prop); return; } this._filters.set({ ...this._filters(), [data.prop]: this.createFilterItem(data, values), }); if (emit) this.emitChanges(); } remove(prop) { this._filters.set({ ...this._filters(), [prop]: undefined, }); this.emitChanges(); } clear() { this._filters.set({}); this.emitChanges(); } create() { const filters = this.filtersArray(); return filters .filter((t) => !!t.values && t.values.length > 0) .map((item) => item.values.map((v) => ({ key: v.key ?? item.key, strictKey: !!item.strictKey || !!v.key, value: this.mapValueToString(v.value), type: v.type, }))) .reduce((pre, cur) => pre.concat(cur), []); } // Emit will be called automatically, use this is you disabled emit on any functions emitChanges() { this._changes$.next(this._filters()); } createFilterItem(opts, value) { const item = { ...this.getKey(opts), values: undefined, prop: opts.prop, label: opts.label, icon: opts.icon, }; return this.setValueForFilterItem(item, value, opts.items); } setValueForFilterItem(item, values, records) { const newValues = values ? values.every((t) => t.value === null || t.value === undefined) ? undefined : values.map((t) => { const value = t.value; if (value instanceof Date && t.controlType === 'date') { if (t.type === 'lower') { value.setHours(23, 59, 59, 999); } else { value.setHours(0, 0, 0, 0); } } return { ...t, value, }; }) : undefined; item.values = newValues; item.formatted = newValues ? this.formatFilterItem(item, records) : undefined; return item; } mapValueToString(value) { if (value instanceof Array) { return value.map((t) => this.mapValueToString(t)); } if (value instanceof Date) { return value.toISOString(); } if (typeof value === 'boolean') { return value ? 'true' : 'false'; } if (typeof value === 'number') { return value.toString(); } return value ?? ''; } mapValueToReadable(filterValue, records) { const value = filterValue.value; if (value instanceof Date) { if (filterValue.controlType === 'datetime') { return DateHelper.format(value, 'HH:mm ,yyyy/MM/dd'); } return DateHelper.format(value); } if (filterValue.displayText && filterValue.displayText.length) { return filterValue.displayText instanceof Array ? filterValue.displayText.join(', ') : filterValue.displayText; } if (records && records instanceof Array) { return ArrayHelper.getFromItemRecord(records, value)?.label ?? ''; } if (typeof value === 'boolean') { return value ? $localize `:@@base.values.trueText:Yes` : $localize `:@@base.values.falseText:No`; } if (typeof value === 'number') { return value.toString(); } return value instanceof Array ? (value.length ? value.join(', ') : '') : value ?? ''; } getKey(data) { if (data.filterable === true) { return { key: data.prop }; } else if (typeof data.filterable === 'string') { return data.filterable !== '' ? { key: data.filterable, strictKey: true } : { key: data.prop }; } return { key: data.prop }; } formatFilterItem(item, records) { let prefix = ''; let suffix = ''; const equal = item.values?.find((t) => t.type === 'equal' || t.type === 'contains'); const greater = item.values?.find((t) => t.type === 'greater'); const lower = item.values?.find((t) => t.type === 'lower'); const equalValue = equal?.value !== undefined && equal?.value !== null ? this.mapValueToReadable(equal, records) : undefined; const greaterValue = greater?.value !== undefined && greater?.value !== null ? this.mapValueToReadable(greater, records) : undefined; const lowerValue = lower?.value !== undefined && lower?.value !== null ? this.mapValueToReadable(lower, records) : undefined; if (equalValue !== undefined && equalValue !== null) { suffix += ` : ${equalValue}`; } else if (greaterValue !== undefined && greaterValue !== null && lowerValue !== undefined && lowerValue !== null) { prefix += `${greaterValue} < `; suffix += ` < ${lowerValue}`; } else if (greaterValue !== undefined && greaterValue !== null) { suffix += ` > ${greaterValue}`; } else if (lowerValue !== undefined && lowerValue !== null) { suffix += ` < ${lowerValue}`; } if (prefix === '' && suffix === '') return undefined; return { full: `${prefix}${item.label}${suffix}`, text: item.label, prefix: prefix, suffix: suffix, }; } } class TableFilterModel extends FilterModel { constructor(initValue, overrideEmptyValue) { super(initValue); this.initValue = initValue; this.overrideEmptyValue = overrideEmptyValue; this.columnFilters = {}; this.columnLabels = {}; this.columnsChanged$ = new BehaviorSubject(undefined); } set(columnProp, values, emit = true) { if (typeof columnProp === 'object') { super.set(columnProp, values, emit); return; } if (!this._columns) return; const col = this._columns.find((t) => t.prop === columnProp); if (!col) { console.error(`Table filter failed with the given prop: '${columnProp}'`); return; } super.set(col, values, emit); } setColumns(columns) { this._columns = columns; this.updateFilters(); this.setOverrideEmpty(); this.columnsChanged$.next(); } getColumnFilters(columnProp) { return this.columnFilters[columnProp]; } setOverrideEmpty() { if (!this.overrideEmptyValue || !this._columns) return; this.overrideEmpty = {}; for (const prop in this.overrideEmptyValue) { const column = this._columns.find((t) => t.prop === prop); if (!column) continue; const value = this.overrideEmptyValue[prop]; const item = this.createFilterItem(column, value); this.overrideEmpty[item.prop] = item; } if (!this.initValue) { this._filters.set(this.overrideEmpty); } } updateFilters() { if (!this._columns) return; this.columnFilters = {}; this.columnLabels = {}; for (const column of this._columns) { let filters; if (column.filterable instanceof Array) { filters = column.filterable; } else if (column.filterable) { switch (column.type ?? 'text') { case 'number': { filters = [ { inputType: 'number', type: 'equal' }, { inputType: 'number', type: 'greater' }, { inputType: 'number', type: 'lower' }, ]; break; } case 'boolean': { filters = [{ type: 'equal', controlType: 'select', items: column.items ?? BooleanValues }]; break; } case 'plate': filters = [{ type: 'equal', controlType: 'plate' }]; break; default: { filters = [ { type: 'contains', controlType: column.items ? 'select' : 'input', items: column.items ?? undefined, }, ]; } } } if (filters) { this.columnLabels[column.prop] = column.label; this.columnFilters[column.prop] = filters; } } } } const ICON_COMPONENT_CONFIG = new InjectionToken('IconComponent'); class CacIconComponent { constructor() { this.disabled = false; this.size = '1.5rem'; this.strokeWidth = 1.9; this.onClick = new EventEmitter(); this.pointerEventsNone = false; this.thisWidth = ''; this.thisHeight = ''; this.isClickable = signal(false); componentWithDefaultConfig(this, ICON_COMPONENT_CONFIG); this.thisWidth = this.size; this.thisHeight = this.size; } ngOnInit() { this.isClickable.set(this.onClick.observed); } ngAfterViewInit() { // @ts-ignore const fetchSub = this.matIcon._currentIconFetch; fetchSub.add(() => { const svgElement = this.matIcon._elementRef.nativeElement.children.item(0); if (!svgElement) return; svgElement.style.strokeWidth = `${this.strokeWidth}`; }); } ngOnChanges(changes) { if (changes['disabled']) { this.pointerEventsNone = this.disabled; } if (changes['size']) { this.thisWidth = this.size; this.thisHeight = this.size; } } onClickEvent(e) { if (this.disabled) return; this.onClick.emit(e); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: CacIconComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.0.7", type: CacIconComponent, isStandalone: true, selector: "cac-icon", inputs: { icon: "icon", disabled: "disabled", size: "size", strokeWidth: "strokeWidth", iconClass: "iconClass", wrapperClass: "wrapperClass" }, outputs: { onClick: "onClick" }, host: { properties: { "class.pointer-events-none": "this.pointerEventsNone", "style.width": "this.thisWidth", "style.height": "this.thisHeight" } }, viewQueries: [{ propertyName: "matIcon", first: true, predicate: ["MatIcon"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div\n role=\"presentation\"\n class=\"ui-icon relative {{ disabled ? 'disabled' : '' }} w-full h-full {{ wrapperClass }}\"\n [class.is-clickable]=\"isClickable()\"\n (click)=\"onClickEvent($event)\"\n >\n @if (!!icon) {\n <mat-icon\n #MatIcon\n class=\"{{ iconClass ?? '' }}\"\n [ngStyle]=\"{ width: size, height: size }\"\n [svgIcon]=\"icon\"\n ></mat-icon>\n }\n <ng-content></ng-content>\n</div>\n", styles: [":host{display:inline-block;color:inherit;position:relative;border-radius:.25rem}:host ::ng-deep .ui-icon{height:inherit;width:inherit;color:inherit;filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s;display:inline-flex;align-items:center;justify-content:center;border-radius:inherit}:host ::ng-deep .ui-icon.is-clickable{cursor:pointer}:host ::ng-deep .ui-icon.is-clickable:hover{background-color:var(--surface-bright)!important;--tw-brightness: brightness(.9) !important;--tw-drop-shadow: drop-shadow(0 1px 1px rgb(0 0 0 / .05)) !important;filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)!important}:host ::ng-deep .ui-icon.is-clickable:hover:active{background-color:var(--surface-container)!important;--tw-brightness: brightness(1) !important;--tw-drop-shadow: drop-shadow(0 0 #0000) !important;filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)!important}:host ::ng-deep .ui-icon.disabled{cursor:default!important;--tw-text-opacity: 1;color:rgb(156 163 175 / var(--tw-text-opacity, 1))}\n"], dependencies: [{ kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i4.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "directive", type: NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: CacIconComponent, decorators: [{ type: Component, args: [{ selector: 'cac-icon', standalone: true, imports: [MatIconModule, NgStyle], template: "<div\n role=\"presentation\"\n class=\"ui-icon relative {{ disabled ? 'disabled' : '' }} w-full h-full {{ wrapperClass }}\"\n [class.is-clickable]=\"isClickable()\"\n (click)=\"onClickEvent($event)\"\n >\n @if (!!icon) {\n <mat-icon\n #MatIcon\n class=\"{{ iconClass ?? '' }}\"\n [ngStyle]=\"{ width: size, height: size }\"\n [svgIcon]=\"icon\"\n ></mat-icon>\n }\n <ng-content></ng-content>\n</div>\n", styles: [":host{display:inline-block;color:inherit;position:relative;border-radius:.25rem}:host ::ng-deep .ui-icon{height:inherit;width:inherit;color:inherit;filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s;display:inline-flex;align-items:center;justify-content:center;border-radius:inherit}:host ::ng-deep .ui-icon.is-clickable{cursor:pointer}:host ::ng-deep .ui-icon.is-clickable:hover{background-color:var(--surface-bright)!important;--tw-brightness: brightness(.9) !important;--tw-drop-shadow: drop-shadow(0 1px 1px rgb(0 0 0 / .05)) !important;filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)!important}:host ::ng-deep .ui-icon.is-clickable:hover:active{background-color:var(--surface-container)!important;--tw-brightness: brightness(1) !important;--tw-drop-shadow: drop-shadow(0 0 #0000) !important;filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)!important}:host ::ng-deep .ui-icon.disabled{cursor:default!important;--tw-text-opacity: 1;color:rgb(156 163 175 / var(--tw-text-opacity, 1))}\n"] }] }], ctorParameters: () => [], propDecorators: { matIcon: [{ type: ViewChild, args: ['MatIcon'] }], icon: [{ type: Input }], disabled: [{ type: Input }], size: [{ type: Input }], strokeWidth: [{ type: Input }], iconClass: [{ type: Input }], wrapperClass: [{ type: Input }], onClick: [{ type: Output }], pointerEventsNone: [{ type: HostBinding, args: ['class.pointer-events-none'] }], thisWidth: [{ type: HostBinding, args: ['style.width'] }], thisHeight: [{ type: HostBinding, args: ['style.height'] }] } }); const BUTTON_COMPONENT_CONFIG = new InjectionToken('CacButtonComponent'); class CacButtonComponent { constructor() { this.iconPosition = 'prefix'; this.appearance = 'filled'; this.tonal = false; this.elevated = false; this.theme = 'primary'; this.disabled = false; this.loadingProp = false; this.iconSize = '1.5rem'; this.padding = '0.5rem 1rem'; this.fitContent = false; this.align = 'center'; // TabIndex this.tab = 0; this.onClick = new EventEmitter(); this.filledClass = true; this.strokedClass = false; this.textClass = false; this.isElevated = false; this.isTonal = false; this.isClicking = false; this.primaryClass = false; this.secondaryClass = false; this.tertiaryClass = false; this.errorClass = false; this.disabledClass = false; this.cursorNotAllowed = false; this.loading = signal(false); this.insufficientPermission = signal(false); componentWithDefaultConfig(this, BUTTON_COMPONENT_CONFIG); toObservable(this.insufficientPermission) .pipe(takeUntilDestroyed()) .subscribe(() => { this.setDisabledClass(); }); } ngOnInit() { this.setTheme(); this.checkPermission(); } ngAfterViewInit() { setTimeout(() => { // Angular... doesn't bind tabIndex in template! this.btnElement.nativeElement.tabIndex = this.tab; }, 5); } ngOnChanges(changes) { if (changes['loading']) { this.loading.set(this.loadingProp ?? false); } if (changes['appearance']) { this.strokedClass = this.appearance === 'stroked'; this.filledClass = this.appearance === 'filled'; this.textClass = this.appearance === 'text'; } if (changes['theme']) { this.setTheme(); } if (changes['elevated']) { this.isElevated = this.elevated; } if (changes['tonal']) { this.isTonal = this.tonal; } this.setDisabledClass(); } onPointerDown() { this.isClicking = true; } onPointerCancel() { this.isClicking = false; } onPointerLeave() { this.isClicking = false; } onPointerUp() { this.isClicking = false; } onClickEvent(e) { if (this.loadingProp || this.disabled || this.insufficientPermission() || this.loading()) return; this.onClick.emit(this.createClickEvent(e)); } createClickEvent(mouseEvent) { const e = { event: mouseEvent ?? new Event('click'), setLoading: (state) => { this.loading.set(state !== undefined ? state : !this.loading()); }, pipe: () => pipe(startWithTap(() => e.setLoading(true)), tap({ next: () => e.setLoading(false), error: () => e.setLoading(false), complete: () => e.setLoading(false), })), }; return e; } setTheme() { this.primaryClass = false; this.secondaryClass = false; this.tertiaryClass = false; this.errorClass = false; if (this.theme === 'custom') return; this[`${this.theme}Class`] = true; } checkPermission() { if (!this.action && !this.permission) return; // this.insufficientPermission.set( // this.permission ? !this.role.hasPermission(this.permission) : !this.role.hasActionPermission(this.action), // ); } setDisabledClass() { const insufficientPermission = this.insufficientPermission(); this.disabledClass = (this.disabled ?? false) || insufficientPermission; if (insufficientPermission) { this.cursorNotAllowed = true; } else { this.cursorNotAllowed = false; } } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: CacButtonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.0.7", type: CacButtonComponent, isStandalone: true, selector: "cac-button", inputs: { icon: "icon", iconPosition: "iconPosition", appearance: "appearance", tonal: "tonal", elevated: "elevated", theme: "theme", disabled: "disabled", loadingProp: ["loading", "loadingProp"], iconSize: "iconSize", padding: "padding", fitContent: "fitContent", align: "align", route: "route", tab: "tab", permission: "permission", action: "action" }, outputs: { onClick: "onClick" }, host: { listeners: { "pointerdown": "onPointerDown($event)", "pointercancel": "onPointerCancel($event)", "pointerleave": "onPointerLeave($event)", "pointerup": "onPointerUp($event)" }, properties: { "class.filled": "this.filledClass", "class.stroked": "this.strokedClass", "class.text": "this.textClass", "class.elevated": "this.isElevated", "class.tonal": "this.isTonal", "class.is-clicking": "this.isClicking", "class.theme-primary": "this.primaryClass", "class.theme-secondary": "this.secondaryClass", "class.theme-tertiary": "this.tertiaryClass", "class.theme-error": "this.errorClass", "class.disabled": "this.disabledClass", "class.cursor-not-allowed": "this.cursorNotAllowed" } }, viewQueries: [{ propertyName: "btnElement", first: true, predicate: ["Button"], descendants: true, read: ElementRef }], usesOnChanges: true, ngImport: i0, template: "<div\n class=\"ui-button-inherit-parent\"\n [matTooltip]=\"insufficientPermission() ? 'You have insufficient permission.' : ''\"\n >\n <button\n #Button\n mat-flat-button\n class=\"ui-button-inherit-parent ui-button\"\n [disabled]=\"(disabled ?? false) || loading() || insufficientPermission()\"\n [ngStyle]=\"{ padding: padding }\"\n (click)=\"onClickEvent($event)\"\n [routerLink]=\"route\"\n [class.fit-content]=\"fitContent\"\n [class.content-align-start]=\"align === 'start'\"\n [class.content-align-center]=\"align === 'center' || align === undefined\"\n [class.content-align-end]=\"align === 'end'\"\n >\n <div class=\"ui-button-content\" [class.loading]=\"loading()\">\n @if (iconPosition === 'prefix') {\n <ng-container [ngTemplateOutlet]=\"Icon\"></ng-container>\n }\n <div class=\"ui-button-content-portal\">\n <ng-content></ng-content>\n </div>\n @if (iconPosition === 'suffix') {\n <ng-container [ngTemplateOutlet]=\"Icon\"></ng-container>\n }\n @if (loading()) {\n <div class=\"ui-button-loading-spinner\">\n <mat-spinner color=\"primary\" diameter=\"16\" class=\"mx-2\"></mat-spinner>\n </div>\n }\n </div>\n </button>\n</div>\n\n<ng-template #Icon>\n @if (!!icon) {\n <cac-icon\n class=\"transition-colors\"\n [icon]=\"icon\"\n [style]=\"'width:' + iconSize + ';height:' + iconSize\"\n ></cac-icon>\n }\n</ng-template>\n", styles: [":host{transition-property:width,height,background-color,opacity,font-size,border,box-shadow,filter;transition-duration:.15s;transition-timing-function:ease;height:2.35rem;max-width:initial;width:auto;border-radius:.25rem;overflow:hidden;font-size:.875rem;font-weight:500;min-width:32px!important;min-height:32px!important;position:relative;display:block}:host.stroked{background-color:transparent}:host.stroked.theme-primary{color:var(--primary-default)}:host.stroked.theme-secondary{color:var(--secondary-default)}:host.stroked.theme-tertiary{color:var(--tertiary-default)}:host.stroked.theme-error{color:var(--error-default)}:host.filled{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}:host.filled.theme-primary{background-color:var(--primary-default)}:host.filled.theme-secondary{background-color:var(--secondary-default)}:host.filled.theme-tertiary{background-color:var(--tertiary-default)}:host.filled.theme-error{background-color:var(--error-default)}:host.text.theme-primary{color:var(--primary-default)}:host.text.theme-secondary{color:var(--secondary-default)}:host.text.theme-tertiary{color:var(--tertiary-default)}:host.text.theme-error{color:var(--error-default)}:host.elevated{--tw-shadow: 0px 1px 2px rgba(0, 0, 0, .3), 0px 2px 6px 2px rgba(0, 0, 0, .15);--tw-shadow-colored: 0px 1px 2px var(--tw-shadow-color), 0px 2px 6px 2px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}:host.stroked{border:1px solid currentColor;border-color:var(--outline)}:host.stroked:hover:not(.disabled){background-color:#1e1e1e03!important;filter:brightness(100%)}:host.stroked:active:hover:not(.disabled){background-color:#1e1e1e08!important}:host.stroked.disabled{--tw-border-opacity: .6 !important;background-color:transparent!important;color:var(--surface-on)!important}:host.filled:hover:not(.disabled){filter:brightness(95%)}:host.filled:hover:not(.disabled) mat-icon{filter:brightness(80%) saturate(95%)}:host.filled:hover:not(.disabled).tonal{--tw-shadow: 0px 1px 2px rgba(0, 0, 0, .3), 0px 2px 6px 2px rgba(0, 0, 0, .15);--tw-shadow-colored: 0px 1px 2px var(--tw-shadow-color), 0px 2px 6px 2px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}:host.filled.disabled{border-color:var(--outline-variant)!important;--tw-bg-opacity: 1 !important;background-color:rgb(115 115 115 / var(--tw-bg-opacity, 1))!important;--tw-bg-opacity: 12% !important;color:var(--surface-on)!important;--tw-text-opacity: 38% !important}:host.text:hover:not(.disabled){background-color:transparent}:host.text:hover:not(.disabled):hover{background-color:var(--primary-50)}:host.text:active:hover:not(.disabled){filter:brightness(95%)}:host.text.disabled{color:var(--outline)!important;opacity:.5!important}:host.elevated.is-clicking{--tw-shadow: 0px 0px 0px rgba(0, 0, 0, 0), 0px 0px 0px 0px rgba(0, 0, 0, 0);--tw-shadow-colored: 0px 0px 0px var(--tw-shadow-color), 0px 0px 0px 0px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.ui-button-inherit-parent{border-radius:inherit!important;min-width:inherit!important;min-height:inherit!important;white-space:normal!important;line-height:normal!important;font-family:inherit!important;font-weight:inherit!important;font-size:inherit!important;background:inherit!important;color:inherit!important;width:100%!important;height:100%!important}:host ::ng-deep .ui-button{transition:none!important;position:static!important;letter-spacing:0!important}:host ::ng-deep .ui-button.content-align-start{justify-content:flex-start!important}:host ::ng-deep .ui-button.content-align-center{justify-content:center!important}:host ::ng-deep .ui-button.content-align-end{justify-content:flex-end!important}:host ::ng-deep .ui-button .ui-button-content{position:relative;display:flex;height:100%;width:100%;flex-direction:row;align-items:center}:host ::ng-deep .ui-button .ui-button-content.loading>*:not(.ui-button-loading-spinner){visibility:hidden!important}:host ::ng-deep .ui-button .ui-button-loading-spinner{position:absolute;pointer-events:none;inset:0;display:flex;flex-direction:column;justify-content:center;align-items:center}:host ::ng-deep .ui-button .ui-button-loading-spinner circle{stroke:currentColor!important;color:inherit!important}:host ::ng-deep .ui-button .ui-button-content-portal{margin-left:.5rem;margin-right:.5rem;display:inline-flex;align-items:center}:host ::ng-deep .ui-button .ui-button-content-portal:empty{display:none}:host ::ng-deep .ui-button.fit-content .ui-button-content,:host ::ng-deep .ui-button.fit-content .ui-button-content-portal,:host ::ng-deep .ui-button.fit-content .mdc-button__label{width:100%;height:100%}:host ::ng-deep .ui-button.fit-content .ui-button-content-portal>*{width:100%;height:100%}\n"], dependencies: [{ kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1.MatButton, selector: " button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ", exportAs: ["matButton"] }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "ngmodule", type: MatProgressSpinnerModule }, { kind: "component", type: i2.MatProgressSpinner, selector: "mat-progress-spinner, mat-spinner", inputs: ["color", "mode", "value", "diameter", "strokeWidth"], exportAs: ["matProgressSpinner"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "directive", type: NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i3.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: CacIconComponent, selector: "cac-icon", inputs: ["icon", "disabled", "size", "strokeWidth", "iconClass", "wrapperClass"], outputs: ["onClick"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: CacButtonComponent, decorators: [{ type: Component, args: [{ selector: 'cac-button', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [ MatButtonModule, RouterLink, MatProgressSpinnerModule, NgTemplateOutlet, MatIconModule, NgStyle, MatTooltipModule, CacIconComponent ], template: "<div\n class=\"ui-button-inherit-parent\"\n [matTooltip]=\"insufficientPermission() ? 'You have insufficient permission.' : ''\"\n >\n <button\n #Button\n mat-flat-button\n class=\"ui-button-inherit-parent ui-button\"\n [disabled]=\"(disabled ?? false) || loading() || insufficientPermission()\"\n [ngStyle]=\"{ padding: padding }\"\n (click)=\"onClickEvent($event)\"\n [routerLink]=\"route\"\n [class.fit-content]=\"fitContent\"\n [class.content-align-start]=\"align === 'start'\"\n [class.content-align-center]=\"align === 'center' || align === undefined\"\n [class.content-align-end]=\"align === 'end'\"\n >\n <div class=\"ui-button-content\" [