@controladad/ng-base
Version:
Everything you need for Angular
909 lines (897 loc) • 561 kB
JavaScript
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\" [