osl-base-extended 6.10.0 → 7.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -24,9 +24,10 @@ import { MAT_DATE_FORMATS } from '@angular/material/core';
|
|
|
24
24
|
import * as i4 from '@ngxmc/datetime-picker';
|
|
25
25
|
import { NgxMatDatetimepicker, NgxMatDatepickerInput } from '@ngxmc/datetime-picker';
|
|
26
26
|
import { MatInputModule } from '@angular/material/input';
|
|
27
|
+
import * as i2$2 from '@angular/cdk/scrolling';
|
|
28
|
+
import { ScrollingModule } from '@angular/cdk/scrolling';
|
|
27
29
|
import { debounceTime as debounceTime$1, distinctUntilChanged as distinctUntilChanged$1, switchMap, tap } from 'rxjs/operators';
|
|
28
30
|
import { MatMenuModule } from '@angular/material/menu';
|
|
29
|
-
import { ScrollingModule } from '@angular/cdk/scrolling';
|
|
30
31
|
import * as i3$1 from '@angular/cdk/drag-drop';
|
|
31
32
|
import { moveItemInArray, DragDropModule } from '@angular/cdk/drag-drop';
|
|
32
33
|
import { TemplatePortal, PortalModule } from '@angular/cdk/portal';
|
|
@@ -3346,7 +3347,7 @@ class OslSetup {
|
|
|
3346
3347
|
customFooterWrapperTpl;
|
|
3347
3348
|
searchbar;
|
|
3348
3349
|
gridRef;
|
|
3349
|
-
|
|
3350
|
+
cardViewport;
|
|
3350
3351
|
// ── Inputs ────────────────────────────────────────────────────
|
|
3351
3352
|
title = '';
|
|
3352
3353
|
columns = [];
|
|
@@ -3376,6 +3377,8 @@ class OslSetup {
|
|
|
3376
3377
|
cardPageSize;
|
|
3377
3378
|
/** Optional custom card template. Context: { $implicit: row, index: number } */
|
|
3378
3379
|
cardTemplate;
|
|
3380
|
+
/** Height (px) of each card row used by the virtual scroll engine. Default: 70. */
|
|
3381
|
+
cardHeightForVirtualscroll = 70;
|
|
3379
3382
|
// ── Outputs ───────────────────────────────────────────────────
|
|
3380
3383
|
onSearch = new EventEmitter();
|
|
3381
3384
|
onAdd = new EventEmitter();
|
|
@@ -3422,6 +3425,7 @@ class OslSetup {
|
|
|
3422
3425
|
get skeletonCardRows() {
|
|
3423
3426
|
return Array.from({ length: 6 });
|
|
3424
3427
|
}
|
|
3428
|
+
trackByCard = (_, row) => row[this.primaryKey] ?? _;
|
|
3425
3429
|
// ── Lifecycle ─────────────────────────────────────────────────
|
|
3426
3430
|
ngOnInit() {
|
|
3427
3431
|
this._loadViewMode();
|
|
@@ -3458,7 +3462,22 @@ class OslSetup {
|
|
|
3458
3462
|
if (this.autoMode) {
|
|
3459
3463
|
// Local pagination: datasource changed (search filtered or initial load), reset
|
|
3460
3464
|
if (!changes['datasource'].firstChange) {
|
|
3461
|
-
this.
|
|
3465
|
+
if (this._cardRestoreTargetPage > 0) {
|
|
3466
|
+
const targetPage = this._cardRestoreTargetPage;
|
|
3467
|
+
this._cardRestoreTargetPage = 0;
|
|
3468
|
+
const targetCount = targetPage * this._effectiveCardPageSize;
|
|
3469
|
+
this.cardDatasource = this.datasource.slice(0, targetCount);
|
|
3470
|
+
this.cardPage = targetPage;
|
|
3471
|
+
this.allCardsLoaded = this.cardDatasource.length >= this.datasource.length;
|
|
3472
|
+
if (this._pendingScrollTop !== null) {
|
|
3473
|
+
const top = this._pendingScrollTop;
|
|
3474
|
+
this._pendingScrollTop = null;
|
|
3475
|
+
setTimeout(() => { this.cardViewport?.scrollToOffset(top); }, 50);
|
|
3476
|
+
}
|
|
3477
|
+
}
|
|
3478
|
+
else {
|
|
3479
|
+
this._loadCardPageLocally(1);
|
|
3480
|
+
}
|
|
3462
3481
|
}
|
|
3463
3482
|
}
|
|
3464
3483
|
else if (this._cardExpectedPage !== 0) {
|
|
@@ -3488,7 +3507,7 @@ class OslSetup {
|
|
|
3488
3507
|
if (this._pendingScrollTop !== null) {
|
|
3489
3508
|
const top = this._pendingScrollTop;
|
|
3490
3509
|
this._pendingScrollTop = null;
|
|
3491
|
-
setTimeout(() => { this.
|
|
3510
|
+
setTimeout(() => { this.cardViewport?.scrollToOffset(top); }, 50);
|
|
3492
3511
|
}
|
|
3493
3512
|
}
|
|
3494
3513
|
}
|
|
@@ -3592,9 +3611,10 @@ class OslSetup {
|
|
|
3592
3611
|
this._cardExpectedPage = nextPage;
|
|
3593
3612
|
this.pageChange.emit({ page: nextPage, pageSize: this._effectiveCardPageSize, searchValue: this.searchbar?.searchControl?.value ?? '' });
|
|
3594
3613
|
}
|
|
3595
|
-
|
|
3596
|
-
const
|
|
3597
|
-
|
|
3614
|
+
onVirtualScrollIndexChange(firstIndex) {
|
|
3615
|
+
const viewportSize = this.cardViewport?.getViewportSize() ?? 0;
|
|
3616
|
+
const visibleCount = viewportSize > 0 ? Math.ceil(viewportSize / this.cardHeightForVirtualscroll) : 10;
|
|
3617
|
+
if (firstIndex + visibleCount + 3 >= this.cardDatasource.length) {
|
|
3598
3618
|
this.loadMoreCards();
|
|
3599
3619
|
}
|
|
3600
3620
|
}
|
|
@@ -3650,15 +3670,21 @@ class OslSetup {
|
|
|
3650
3670
|
this.cardPage = 0;
|
|
3651
3671
|
this.allCardsLoaded = false;
|
|
3652
3672
|
if (this.autoMode) {
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
3673
|
+
if (this.datasource.length > 0) {
|
|
3674
|
+
// Data already available — restore immediately
|
|
3675
|
+
const targetCount = state.page * this._effectiveCardPageSize;
|
|
3676
|
+
this.cardDatasource = this.datasource.slice(0, targetCount);
|
|
3677
|
+
this.cardPage = state.page;
|
|
3678
|
+
this.allCardsLoaded = this.cardDatasource.length >= this.datasource.length;
|
|
3679
|
+
if (this._pendingScrollTop !== null) {
|
|
3680
|
+
const top = this._pendingScrollTop;
|
|
3681
|
+
this._pendingScrollTop = null;
|
|
3682
|
+
setTimeout(() => { this.cardViewport?.scrollToOffset(top); }, 50);
|
|
3683
|
+
}
|
|
3684
|
+
}
|
|
3685
|
+
else {
|
|
3686
|
+
// Datasource not yet populated — defer restore to ngOnChanges
|
|
3687
|
+
this._cardRestoreTargetPage = state.page;
|
|
3662
3688
|
}
|
|
3663
3689
|
this.onStateRestored.emit(state);
|
|
3664
3690
|
}
|
|
@@ -3779,7 +3805,7 @@ class OslSetup {
|
|
|
3779
3805
|
pageSize: this.viewMode === 'card' ? this._effectiveCardPageSize : (this.gridRef?.pageSize ?? this.pageSize),
|
|
3780
3806
|
searchValue: this.searchbar?.searchControl?.value ?? '',
|
|
3781
3807
|
scrollTop: this.viewMode === 'card'
|
|
3782
|
-
? (this.
|
|
3808
|
+
? (this.cardViewport?.measureScrollOffset('top') ?? 0)
|
|
3783
3809
|
: (this.gridRef?.getScrollTop() ?? 0),
|
|
3784
3810
|
highlightedRow: row,
|
|
3785
3811
|
});
|
|
@@ -3847,11 +3873,11 @@ class OslSetup {
|
|
|
3847
3873
|
});
|
|
3848
3874
|
}
|
|
3849
3875
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: OslSetup, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3850
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: OslSetup, isStandalone: false, selector: "osl-setup", inputs: { title: "title", columns: "columns", datasource: "datasource", isPaginated: "isPaginated", pageSize: "pageSize", autoMode: "autoMode", tableHeight: "tableHeight", totalRecords: "totalRecords", loading: "loading", dialogWidth: "dialogWidth", formElements: "formElements", beforeDisplay: "beforeDisplay", onAddEditFn: "onAddEditFn", isLister: "isLister", canAdd: "canAdd", canEdit: "canEdit", canDelete: "canDelete", moreMenuActions: "moreMenuActions", customFormFooter: "customFormFooter", customHeaderTemp: "customHeaderTemp", partialCustomHeaderTemp: "partialCustomHeaderTemp", stateKey: "stateKey", primaryKey: "primaryKey", onSave: "onSave", cardPageSize: "cardPageSize", cardTemplate: "cardTemplate" }, outputs: { onSearch: "onSearch", onAdd: "onAdd", onEdit: "onEdit", onDelete: "onDelete", pageChange: "pageChange", pageSizeChange: "pageSizeChange", sortChange: "sortChange", onRowClick: "onRowClick", onStateRestored: "onStateRestored" }, host: { listeners: { "document:click": "onDocumentClick()" } }, viewQueries: [{ propertyName: "formBodyTpl", first: true, predicate: ["formBodyTpl"], descendants: true }, { propertyName: "formFooterTpl", first: true, predicate: ["formFooterTpl"], descendants: true }, { propertyName: "customFooterWrapperTpl", first: true, predicate: ["customFooterWrapperTpl"], descendants: true }, { propertyName: "searchbar", first: true, predicate: ["searchbar"], descendants: true }, { propertyName: "gridRef", first: true, predicate: ["gridRef"], descendants: true }, { propertyName: "cardContainerRef", first: true, predicate: ["cardContainerRef"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"p-2\">\n\n <!-- \u2500\u2500 Header \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div class=\"osl-setup-header\">\n <h5 class=\"mb-0\">{{ title }}</h5>\n <div class=\"d-flex align-items-center gap-2\">\n <osl-searchbar #searchbar class=\"mx-2\" (onSearch)=\"onSearchSetup($event)\"></osl-searchbar>\n\n <!-- View Toggle -->\n @if(!isLister){\n <div class=\"osl-view-toggle\">\n <button class=\"osl-view-toggle-btn\" [class.osl-view-toggle-btn--active]=\"viewMode === 'table'\"\n (click)=\"toggleView('table')\" title=\"Table view\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"2\"/>\n <line x1=\"3\" y1=\"9\" x2=\"21\" y2=\"9\"/>\n <line x1=\"3\" y1=\"15\" x2=\"21\" y2=\"15\"/>\n <line x1=\"9\" y1=\"9\" x2=\"9\" y2=\"21\"/>\n </svg>\n </button>\n <button class=\"osl-view-toggle-btn\" [class.osl-view-toggle-btn--active]=\"viewMode === 'card'\"\n (click)=\"toggleView('card')\" title=\"Card view\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <rect x=\"3\" y=\"3\" width=\"7\" height=\"7\" rx=\"1.5\"/>\n <rect x=\"14\" y=\"3\" width=\"7\" height=\"7\" rx=\"1.5\"/>\n <rect x=\"3\" y=\"14\" width=\"7\" height=\"7\" rx=\"1.5\"/>\n <rect x=\"14\" y=\"14\" width=\"7\" height=\"7\" rx=\"1.5\"/>\n </svg>\n </button>\n </div>\n }\n\n @if (!isLister && canAdd) {\n <osl-button variant=\"secondary\" size=\"sm\" [label]=\"'Add ' + title\" (clickEv)=\"openAddDialog()\"></osl-button>\n }\n </div>\n </div>\n\n <!-- \u2500\u2500 Table View \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (viewMode === 'table') {\n <div class=\"osl-setup-body my-2\">\n <osl-grid\n #gridRef\n [columns]=\"columnsWithActions\"\n [(datasource)]=\"datasource\"\n [isPaginated]=\"isPaginated\"\n [pageSize]=\"pageSize\"\n [autoMode]=\"autoMode\"\n [tableHeight]=\"tableHeight\"\n [totalRecords]=\"totalRecords\"\n [loading]=\"loading\"\n [moreMenuActions]=\"moreMenuActions\"\n [canEdit]=\"canEdit\"\n [canDelete]=\"canDelete\"\n [highlightedRow]=\"restoredRow\"\n [primaryKey]=\"primaryKey\"\n (editClick)=\"openEditDialog($event)\"\n (deleteClick)=\"onDeleteClick($event)\"\n (pageChange)=\"onPageChange(pageChange, $event)\"\n (pageSizeChange)=\"onPageChange(pageSizeChange, $event)\"\n (sortChange)=\"sortChange.emit($event)\"\n [isSelectable]=\"isLister\"\n (onRowClick)=\"onRowClick.emit($event)\"\n />\n </div>\n }\n\n <!-- \u2500\u2500 Card View \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (viewMode === 'card') {\n <div #cardContainerRef class=\"osl-card-container my-2\" [style.height]=\"tableHeight\" (scroll)=\"onCardScroll($event)\">\n\n <!-- Skeleton cards on initial load -->\n @if (loading && cardDatasource.length === 0) {\n <div class=\"osl-card-grid\">\n @for (sk of skeletonCardRows; track $index) {\n <div class=\"osl-card osl-card--skeleton\">\n <div class=\"osl-card-inner\">\n <div class=\"osl-card-header\">\n <div class=\"osl-card-avatar osl-card-avatar--skeleton\"></div>\n <div style=\"flex:1\">\n <div class=\"osl-sk-line osl-sk-line--title\"></div>\n </div>\n </div>\n <div class=\"osl-card-divider\"></div>\n <div class=\"osl-card-body\">\n <div class=\"osl-card-field\"><div class=\"osl-sk-line osl-sk-line--label\"></div><div class=\"osl-sk-line\"></div></div>\n <div class=\"osl-card-field\"><div class=\"osl-sk-line osl-sk-line--label\"></div><div class=\"osl-sk-line osl-sk-line--short\"></div></div>\n <div class=\"osl-card-field\"><div class=\"osl-sk-line osl-sk-line--label\"></div><div class=\"osl-sk-line\"></div></div>\n </div>\n </div>\n </div>\n }\n </div>\n }\n\n <!-- Empty state -->\n @else if (cardDatasource.length === 0 && !loading) {\n <div class=\"osl-card-empty\">\n <svg class=\"osl-card-empty-svg\" viewBox=\"0 0 180 160\" fill=\"none\">\n <ellipse cx=\"90\" cy=\"140\" rx=\"60\" ry=\"8\" fill=\"#f3f4f6\"/>\n <rect x=\"30\" y=\"30\" width=\"120\" height=\"96\" rx=\"10\" fill=\"#f9fafb\" stroke=\"#e5e7eb\" stroke-width=\"2\"/>\n <rect x=\"44\" y=\"48\" width=\"92\" height=\"10\" rx=\"5\" fill=\"#e5e7eb\"/>\n <rect x=\"44\" y=\"66\" width=\"72\" height=\"8\" rx=\"4\" fill=\"#f3f4f6\"/>\n <rect x=\"44\" y=\"82\" width=\"84\" height=\"8\" rx=\"4\" fill=\"#f3f4f6\"/>\n <rect x=\"44\" y=\"98\" width=\"56\" height=\"8\" rx=\"4\" fill=\"#f3f4f6\"/>\n <circle cx=\"140\" cy=\"108\" r=\"26\" fill=\"#fff\" stroke=\"#e5e7eb\" stroke-width=\"2\"/>\n <line x1=\"133\" y1=\"101\" x2=\"147\" y2=\"115\" stroke=\"#d1d5db\" stroke-width=\"2.5\" stroke-linecap=\"round\"/>\n <line x1=\"147\" y1=\"101\" x2=\"133\" y2=\"115\" stroke=\"#d1d5db\" stroke-width=\"2.5\" stroke-linecap=\"round\"/>\n </svg>\n <p class=\"osl-card-empty-title\">No records found</p>\n <p class=\"osl-card-empty-desc\">Try adjusting your search or filter criteria</p>\n </div>\n }\n\n <!-- Card grid -->\n @else {\n <div class=\"osl-card-grid\">\n\n @if (cardTemplate) {\n @for (row of cardDatasource; track row[primaryKey]; let i = $index) {\n <ng-container *ngTemplateOutlet=\"cardTemplate; context: { $implicit: row, index: i }\"></ng-container>\n }\n } @else {\n @for (row of cardDatasource; track row[primaryKey]; let i = $index) {\n <div class=\"osl-card\"\n [class.osl-card--highlighted]=\"isHighlightedCard(row)\"\n [class.osl-card--selectable]=\"isLister\"\n (click)=\"isLister ? onRowClick.emit(row) : null\">\n\n <div class=\"osl-card-inner\">\n\n <!-- Card header -->\n <div class=\"osl-card-header\">\n <!-- Avatar circle -->\n @if (cardTitleColumn) {\n <div class=\"osl-card-avatar\">{{ getCardInitial(row) }}</div>\n }\n\n <!-- Title -->\n <div class=\"osl-card-title-wrap\">\n <span class=\"osl-card-title\">\n @if (cardTitleColumn) {\n @switch (cardTitleColumn.displayType) {\n @case ('date') { {{ row[cardTitleColumn.key] | date }} }\n @case ('datetime') { {{ row[cardTitleColumn.key] | date:'medium' }} }\n @case ('time') { {{ row[cardTitleColumn.key] | date:'shortTime' }} }\n @case ('customDateFormat') { {{ row[cardTitleColumn.key] | date:cardTitleColumn.customDateFormat }} }\n @default { {{ getCellValue(row, cardTitleColumn) }} }\n }\n }\n </span>\n </div>\n\n <!-- Action buttons -->\n @if (!isLister && ((hasForm && (canEdit || canDelete)) || (moreMenuActions.length > 0 && hasVisibleActions(row)))) {\n <div class=\"osl-card-actions\">\n\n <!-- More-actions trigger (menu rendered at root to avoid transform containment) -->\n @if (moreMenuActions.length > 0 && hasVisibleActions(row)) {\n <button class=\"osl-card-action-btn osl-card-action-btn--more\"\n (click)=\"toggleCardMenu(i, $event)\" title=\"More actions\">\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <circle cx=\"12\" cy=\"5\" r=\"2.2\"/>\n <circle cx=\"12\" cy=\"12\" r=\"2.2\"/>\n <circle cx=\"12\" cy=\"19\" r=\"2.2\"/>\n </svg>\n </button>\n }\n\n <!-- Edit -->\n @if (hasForm && canEdit) {\n <button class=\"osl-card-action-btn osl-card-action-btn--edit\"\n (click)=\"$event.stopPropagation(); openEditDialog(row)\" title=\"Edit\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7\"/>\n <path d=\"M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z\"/>\n </svg>\n </button>\n }\n\n <!-- Delete -->\n @if (hasForm && canDelete) {\n <button class=\"osl-card-action-btn osl-card-action-btn--delete\"\n (click)=\"$event.stopPropagation(); onDeleteClick(row)\" title=\"Delete\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polyline points=\"3 6 5 6 21 6\"/>\n <path d=\"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2\"/>\n </svg>\n </button>\n }\n </div>\n }\n </div>\n\n <!-- Divider -->\n <div class=\"osl-card-divider\"></div>\n\n <!-- Card body: remaining fields -->\n <div class=\"osl-card-body\">\n @for (col of cardBodyColumns; track col.key) {\n <div class=\"osl-card-field\">\n <span class=\"osl-card-label\">{{ col.label }}</span>\n <span class=\"osl-card-value\" [class.link]=\"col.displayType === 'link'\" (click)=\"col.click ? col.click(row, col) : null\">\n @switch (col.displayType) {\n @case ('date') { {{ row[col.key] | date }} }\n @case ('datetime') { {{ row[col.key] | date:'medium' }} }\n @case ('time') { {{ row[col.key] | date:'shortTime' }} }\n @case ('customDateFormat') { {{ row[col.key] | date:col.customDateFormat }} }\n\n @default { {{ getCellValue(row, col) }} }\n }\n </span>\n </div>\n }\n </div>\n\n </div>\n </div>\n }\n }\n </div>\n\n <!-- Infinite scroll bottom loader -->\n @if (loading) {\n <div class=\"osl-card-loader\">\n <div class=\"osl-card-loader-track\">\n <div class=\"osl-card-loader-bar\"></div>\n </div>\n <span class=\"osl-card-loader-text\">Loading more\u2026</span>\n </div>\n }\n\n <!-- All loaded footer -->\n @if (allCardsLoaded && !loading && cardDatasource.length > 0) {\n <div class=\"osl-card-all-loaded\">\n <span class=\"osl-card-all-loaded-line\"></span>\n <span class=\"osl-card-all-loaded-text\">All {{ totalRecords || cardDatasource.length }} records loaded</span>\n <span class=\"osl-card-all-loaded-line\"></span>\n </div>\n }\n }\n </div>\n }\n\n</div>\n\n<!-- Dialog: Form Body -->\n<ng-template #formBodyTpl>\n <osl-dynamic-form\n [skeletonLoading]=\"formLoading\"\n [elements]=\"formElements\"\n [(model)]=\"dialogModel\"\n ></osl-dynamic-form>\n</ng-template>\n\n<!-- Dialog: Form Footer -->\n<ng-template #formFooterTpl>\n <div class=\"osl-setup-dialog-footer\">\n <osl-button [loading]=\"saveLoading\" variant=\"secondary\" label=\"Save\" (click)=\"saveDialog()\"></osl-button>\n </div>\n</ng-template>\n\n<ng-template #customFooterWrapperTpl let-data>\n <ng-container *ngTemplateOutlet=\"customFormFooter!; context: { $implicit: { dialogModel: dialogModel, dialogMode: dialogMode, dialogRef: data.dialogRef } }\"></ng-container>\n</ng-template>\n\n<!-- Floating card more-actions menu \u2014 rendered outside card DOM to avoid transform containment -->\n@if (viewMode === 'card' && cardOpenMenuIndex !== null && moreMenuActions.length > 0) {\n <div class=\"osl-card-menu\"\n [style.top.px]=\"cardMenuPosition.top\"\n [style.left.px]=\"cardMenuPosition.left\"\n (click)=\"$event.stopPropagation()\">\n <div class=\"osl-card-menu-header\">Actions</div>\n @for (action of moreMenuActions; track $index) {\n @if (!action.hideIf || !action.hideIf(cardDatasource[cardOpenMenuIndex])) {\n <button class=\"osl-card-menu-item\"\n (click)=\"action.click(cardDatasource[cardOpenMenuIndex]); cardOpenMenuIndex = null\">\n <span class=\"osl-card-menu-dot\"></span>\n {{ action.labelIf ? action.labelIf(cardDatasource[cardOpenMenuIndex]) : action.label }}\n </button>\n }\n }\n </div>\n}\n", styles: [".osl-setup-header{display:flex;align-items:center;justify-content:space-between}.osl-setup-dialog-footer{display:flex;align-items:center;justify-content:flex-end;gap:10px;width:100%}.dialog-cancel-btn{display:flex;align-items:center;gap:6px;height:38px;font-size:13px;font-weight:500;border-color:var(--osl-border-color, #d1d5db);color:var(--osl-secondary, #6b7280);border-radius:var(--osl-border-radius, 4px);padding:0 16px;transition:all .2s ease}.dialog-cancel-btn mat-icon{font-size:17px;width:17px;height:17px}.dialog-cancel-btn:hover{border-color:#9ca3af;background:#f9fafb;color:#374151}.dialog-save-btn{display:flex;align-items:center;gap:6px;height:38px;font-size:13px;font-weight:500;background:linear-gradient(135deg,var(--osl-primary, #2563eb),#3b82f6);color:#fff;border-radius:var(--osl-border-radius, 4px);padding:0 18px;transition:all .2s ease;box-shadow:0 2px 8px #2563eb40}.dialog-save-btn mat-icon{font-size:17px;width:17px;height:17px}.dialog-save-btn:hover{background:linear-gradient(135deg,var(--osl-primary-hover, #1d4ed8),#2563eb);box-shadow:0 4px 14px #2563eb66;transform:translateY(-1px)}.dialog-save-btn:active{transform:translateY(0);box-shadow:0 2px 8px #2563eb40}.osl-view-toggle{display:flex;border:1.5px solid var(--osl-border-color, #e5e7eb);border-radius:8px;overflow:hidden;flex-shrink:0;background:#fff}.osl-view-toggle-btn{display:inline-flex;align-items:center;justify-content:center;width:34px;height:34px;border:none;background:transparent;cursor:pointer;color:#9ca3af;transition:background .15s,color .15s;padding:0}.osl-view-toggle-btn svg{width:16px;height:16px;transition:stroke .15s}.osl-view-toggle-btn:first-child{border-right:1.5px solid var(--osl-border-color, #e5e7eb)}.osl-view-toggle-btn--active{background:var(--osl-primary, #6366f1);color:#fff}.osl-view-toggle-btn--active svg{stroke:#fff}.osl-view-toggle-btn:not(.osl-view-toggle-btn--active):hover{background:#f3f4f6;color:#374151}.osl-card-container{overflow-y:auto;overflow-x:hidden;border-radius:12px;padding:4px 2px 16px;box-sizing:border-box}.osl-card-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:16px;padding:4px}@keyframes osl-card-enter{0%{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}.osl-card-grid .osl-card:nth-child(1){animation-delay:0s}.osl-card-grid .osl-card:nth-child(2){animation-delay:.04s}.osl-card-grid .osl-card:nth-child(3){animation-delay:.08s}.osl-card-grid .osl-card:nth-child(4){animation-delay:.12s}.osl-card-grid .osl-card:nth-child(5){animation-delay:.16s}.osl-card-grid .osl-card:nth-child(6){animation-delay:.2s}.osl-card-grid .osl-card:nth-child(7){animation-delay:.24s}.osl-card-grid .osl-card:nth-child(8){animation-delay:.28s}.osl-card-grid .osl-card:nth-child(9){animation-delay:.32s}.osl-card-grid .osl-card:nth-child(10){animation-delay:.36s}.osl-card-grid .osl-card:nth-child(11){animation-delay:.4s}.osl-card-grid .osl-card:nth-child(12){animation-delay:.44s}.osl-card{position:relative;background:#fff;border-radius:14px;border:1px solid #eaecf0;box-shadow:0 1px 4px #1018280f,0 1px 2px #1018280a;overflow:hidden;transition:transform .22s cubic-bezier(.34,1.56,.64,1),box-shadow .22s ease;animation:osl-card-enter .28s ease both}.osl-card:nth-child(6n+1){--card-accent: #6366f1;--card-accent-bg: #eef2ff}.osl-card:nth-child(6n+2){--card-accent: #0ea5e9;--card-accent-bg: #f0f9ff}.osl-card:nth-child(6n+3){--card-accent: #10b981;--card-accent-bg: #ecfdf5}.osl-card:nth-child(6n+4){--card-accent: #f59e0b;--card-accent-bg: #fffbeb}.osl-card:nth-child(6n+5){--card-accent: #f43f5e;--card-accent-bg: #fff1f2}.osl-card:nth-child(6n){--card-accent: #8b5cf6;--card-accent-bg: #faf5ff}.osl-card:before{content:\"\";position:absolute;top:0;left:0;bottom:0;width:4px;background:var(--card-accent, var(--osl-primary, #6366f1))}.osl-card:hover{transform:translateY(-4px);box-shadow:0 12px 32px #1018281a,0 4px 12px #1018280f;border-color:#d0d5dd}.osl-card--highlighted{outline:2px solid var(--osl-primary, #6366f1);outline-offset:2px;animation:osl-card-enter .28s ease both,osl-card-highlight-pulse 2.5s ease-out .3s both}.osl-card--selectable{cursor:pointer}.osl-card--skeleton{pointer-events:none;animation:osl-card-enter .28s ease both}.osl-card--skeleton:hover{transform:none;box-shadow:0 1px 4px #1018280f}@keyframes osl-card-highlight-pulse{0%{box-shadow:0 0 0 4px #6366f14d}to{box-shadow:none}}.osl-card-inner{padding:16px 16px 16px 20px;display:flex;flex-direction:column;height:100%}.osl-card-header{display:flex;align-items:center;gap:12px;margin-bottom:0}.osl-card-avatar{width:40px;height:40px;border-radius:10px;background:var(--card-accent, var(--osl-primary, #6366f1));color:#fff;font-size:16px;font-weight:700;display:flex;align-items:center;justify-content:center;flex-shrink:0;letter-spacing:0;box-shadow:0 4px 10px #00000026}.osl-card-avatar--skeleton{background:#e5e7eb;box-shadow:none;animation:osl-sk-pulse 1.4s ease-in-out infinite}.osl-card-title-wrap{flex:1;min-width:0}.osl-card-title{display:block;font-weight:700;font-size:14px;color:#101828;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;line-height:1.4}.osl-card-actions{display:flex;align-items:center;gap:6px;flex-shrink:0}.osl-card-action-btn{display:inline-flex;align-items:center;justify-content:center;width:30px;height:30px;border-radius:8px;border:1.5px solid #e4e7ec;background:#fff;cursor:pointer;transition:background .15s,border-color .15s,transform .15s,box-shadow .15s;padding:0}.osl-card-action-btn svg{width:14px;height:14px;transition:stroke .15s,fill .15s}.osl-card-action-btn--more svg{fill:#9ca3af;stroke:none}.osl-card-action-btn--more:hover{background:var(--card-accent-bg, #eef2ff);border-color:var(--card-accent, var(--osl-primary, #6366f1));box-shadow:0 2px 6px #00000014;transform:scale(1.05)}.osl-card-action-btn--more:hover svg{fill:var(--card-accent, var(--osl-primary, #6366f1))}.osl-card-action-btn--edit svg{stroke:#6b7280}.osl-card-action-btn--edit:hover{background:var(--card-accent-bg, #eef2ff);border-color:var(--card-accent, var(--osl-primary, #6366f1));box-shadow:0 2px 6px #00000014;transform:scale(1.05)}.osl-card-action-btn--edit:hover svg{stroke:var(--card-accent, var(--osl-primary, #6366f1))}.osl-card-action-btn--delete svg{stroke:#9ca3af}.osl-card-action-btn--delete:hover{background:#fff1f2;border-color:#f43f5e;box-shadow:0 2px 6px #f43f5e26;transform:scale(1.05)}.osl-card-action-btn--delete:hover svg{stroke:#f43f5e}.osl-card-divider{height:1px;background:#f2f4f7;margin:14px 0}.osl-card-body{display:flex;flex-direction:column;gap:10px;flex:1}.osl-card-field{display:grid;grid-template-columns:100px 1fr;gap:6px;align-items:baseline}.osl-card-label{font-size:10px;font-weight:600;color:#9ca3af;text-transform:uppercase;letter-spacing:.06em;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.osl-card-value{font-size:13px;font-weight:500;color:#374151;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}.osl-card-value.link{text-decoration:underline;color:#2563eb;cursor:pointer}.osl-card-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:320px;gap:12px;padding:40px 20px}.osl-card-empty-svg{width:180px;height:auto;opacity:.85}.osl-card-empty-title{font-size:16px;font-weight:600;color:#374151;margin:4px 0 0;text-align:center}.osl-card-empty-desc{font-size:13px;color:#9ca3af;margin:0;text-align:center}.osl-card-loader{display:flex;flex-direction:column;align-items:center;gap:10px;padding:28px 20px 16px}.osl-card-loader-track{width:160px;height:3px;background:#f3f4f6;border-radius:99px;overflow:hidden}.osl-card-loader-bar{height:100%;width:40%;background:linear-gradient(90deg,transparent,var(--osl-primary, #6366f1),transparent);border-radius:99px;animation:osl-loader-sweep 1.2s ease-in-out infinite}@keyframes osl-loader-sweep{0%{transform:translate(-200%)}to{transform:translate(500%)}}.osl-card-loader-text{font-size:12px;color:#9ca3af;font-weight:500}.osl-card-all-loaded{display:flex;align-items:center;gap:12px;padding:20px 16px 8px;justify-content:center}.osl-card-all-loaded-line{flex:1;max-width:80px;height:1px;background:#e5e7eb}.osl-card-all-loaded-text{font-size:11px;color:#d1d5db;font-weight:600;text-transform:uppercase;letter-spacing:.06em;white-space:nowrap}@keyframes osl-sk-pulse{0%,to{opacity:1}50%{opacity:.4}}.osl-sk-line{height:12px;background:#e9eaec;border-radius:6px;animation:osl-sk-pulse 1.4s ease-in-out infinite;width:80%}.osl-sk-line--title{height:14px;width:65%}.osl-sk-line--label{width:40%;height:10px}.osl-sk-line--short{width:50%}.osl-card-menu{position:fixed;z-index:9999;min-width:196px;background:#fff;border:1px solid #eaecf0;border-radius:12px;box-shadow:0 12px 32px #10182824,0 4px 12px #1018280f;overflow:hidden;animation:osl-menu-pop .16s cubic-bezier(.16,1,.3,1)}@keyframes osl-menu-pop{0%{opacity:0;transform:translateY(-8px) scale(.96)}to{opacity:1;transform:translateY(0) scale(1)}}.osl-card-menu-header{padding:10px 14px 7px;font-size:10px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:#9ca3af;border-bottom:1px solid #f2f4f7;-webkit-user-select:none;user-select:none}.osl-card-menu-item{display:flex;align-items:center;gap:10px;width:100%;padding:10px 14px;background:transparent;border:none;border-left:3px solid transparent;border-bottom:1px solid #f9fafb;text-align:left;font-size:13px;font-weight:500;font-family:inherit;color:#374151;cursor:pointer;white-space:nowrap;transition:background .12s,color .12s,border-left-color .12s}.osl-card-menu-item:last-child{border-bottom:none}.osl-card-menu-item:hover{background:var(--card-accent-bg, #f5f3ff);color:var(--card-accent, var(--osl-primary, #6366f1));border-left-color:var(--card-accent, var(--osl-primary, #6366f1))}.osl-card-menu-item:hover .osl-card-menu-dot{background:var(--card-accent, var(--osl-primary, #6366f1));box-shadow:0 0 0 3px #6366f129}.osl-card-menu-dot{flex-shrink:0;width:6px;height:6px;border-radius:50%;background:#d1d5db;transition:background .12s,box-shadow .12s}\n"], dependencies: [{ kind: "directive", type: i1$2.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: DynamicForm, selector: "osl-dynamic-form", inputs: ["elements", "model", "skeletonLoading", "skeletonTheme"], outputs: ["modelChange"] }, { kind: "component", type: OslButton, selector: "osl-button", inputs: ["label", "icon", "variant", "size", "disabled", "loading", "type", "fullWidth"], outputs: ["clickEv"] }, { kind: "component", type: OslSearchbar, selector: "osl-searchbar", inputs: ["label"], outputs: ["onSearch"] }, { kind: "component", type: OslGrid, selector: "osl-grid", inputs: ["columns", "datasource", "isPaginated", "pageSize", "autoMode", "totalRecords", "tableHeight", "loading", "isSelectable", "moreMenuActions", "canEdit", "canDelete", "highlightedRow", "primaryKey"], outputs: ["datasourceChange", "pageChange", "pageSizeChange", "sortChange", "editClick", "deleteClick", "onRowClick"] }, { kind: "pipe", type: i1$2.DatePipe, name: "date" }] });
|
|
3876
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: OslSetup, isStandalone: false, selector: "osl-setup", inputs: { title: "title", columns: "columns", datasource: "datasource", isPaginated: "isPaginated", pageSize: "pageSize", autoMode: "autoMode", tableHeight: "tableHeight", totalRecords: "totalRecords", loading: "loading", dialogWidth: "dialogWidth", formElements: "formElements", beforeDisplay: "beforeDisplay", onAddEditFn: "onAddEditFn", isLister: "isLister", canAdd: "canAdd", canEdit: "canEdit", canDelete: "canDelete", moreMenuActions: "moreMenuActions", customFormFooter: "customFormFooter", customHeaderTemp: "customHeaderTemp", partialCustomHeaderTemp: "partialCustomHeaderTemp", stateKey: "stateKey", primaryKey: "primaryKey", onSave: "onSave", cardPageSize: "cardPageSize", cardTemplate: "cardTemplate", cardHeightForVirtualscroll: "cardHeightForVirtualscroll" }, outputs: { onSearch: "onSearch", onAdd: "onAdd", onEdit: "onEdit", onDelete: "onDelete", pageChange: "pageChange", pageSizeChange: "pageSizeChange", sortChange: "sortChange", onRowClick: "onRowClick", onStateRestored: "onStateRestored" }, host: { listeners: { "document:click": "onDocumentClick()" } }, viewQueries: [{ propertyName: "formBodyTpl", first: true, predicate: ["formBodyTpl"], descendants: true }, { propertyName: "formFooterTpl", first: true, predicate: ["formFooterTpl"], descendants: true }, { propertyName: "customFooterWrapperTpl", first: true, predicate: ["customFooterWrapperTpl"], descendants: true }, { propertyName: "searchbar", first: true, predicate: ["searchbar"], descendants: true }, { propertyName: "gridRef", first: true, predicate: ["gridRef"], descendants: true }, { propertyName: "cardViewport", first: true, predicate: ["cardViewport"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"p-2\">\n\n <!-- \u2500\u2500 Header \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div class=\"osl-setup-header\">\n <h5 class=\"mb-0\">{{ title }}</h5>\n <div class=\"d-flex align-items-center gap-2\">\n <osl-searchbar #searchbar class=\"mx-2\" (onSearch)=\"onSearchSetup($event)\"></osl-searchbar>\n\n <!-- View Toggle -->\n @if(!isLister){\n <div class=\"osl-view-toggle\">\n <button class=\"osl-view-toggle-btn\" [class.osl-view-toggle-btn--active]=\"viewMode === 'table'\"\n (click)=\"toggleView('table')\" title=\"Table view\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"2\"/>\n <line x1=\"3\" y1=\"9\" x2=\"21\" y2=\"9\"/>\n <line x1=\"3\" y1=\"15\" x2=\"21\" y2=\"15\"/>\n <line x1=\"9\" y1=\"9\" x2=\"9\" y2=\"21\"/>\n </svg>\n </button>\n <button class=\"osl-view-toggle-btn\" [class.osl-view-toggle-btn--active]=\"viewMode === 'card'\"\n (click)=\"toggleView('card')\" title=\"Card view\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <rect x=\"3\" y=\"3\" width=\"7\" height=\"7\" rx=\"1.5\"/>\n <rect x=\"14\" y=\"3\" width=\"7\" height=\"7\" rx=\"1.5\"/>\n <rect x=\"3\" y=\"14\" width=\"7\" height=\"7\" rx=\"1.5\"/>\n <rect x=\"14\" y=\"14\" width=\"7\" height=\"7\" rx=\"1.5\"/>\n </svg>\n </button>\n </div>\n }\n\n @if (!isLister && canAdd) {\n <osl-button variant=\"secondary\" size=\"sm\" [label]=\"'Add ' + title\" (clickEv)=\"openAddDialog()\"></osl-button>\n }\n </div>\n </div>\n\n <!-- \u2500\u2500 Table View \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (viewMode === 'table') {\n <div class=\"osl-setup-body my-2\">\n <osl-grid\n #gridRef\n [columns]=\"columnsWithActions\"\n [(datasource)]=\"datasource\"\n [isPaginated]=\"isPaginated\"\n [pageSize]=\"pageSize\"\n [autoMode]=\"autoMode\"\n [tableHeight]=\"tableHeight\"\n [totalRecords]=\"totalRecords\"\n [loading]=\"loading\"\n [moreMenuActions]=\"moreMenuActions\"\n [canEdit]=\"canEdit\"\n [canDelete]=\"canDelete\"\n [highlightedRow]=\"restoredRow\"\n [primaryKey]=\"primaryKey\"\n (editClick)=\"openEditDialog($event)\"\n (deleteClick)=\"onDeleteClick($event)\"\n (pageChange)=\"onPageChange(pageChange, $event)\"\n (pageSizeChange)=\"onPageChange(pageSizeChange, $event)\"\n (sortChange)=\"sortChange.emit($event)\"\n [isSelectable]=\"isLister\"\n (onRowClick)=\"onRowClick.emit($event)\"\n />\n </div>\n }\n\n <!-- \u2500\u2500 Card View \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (viewMode === 'card') {\n\n <!-- Skeleton on initial load -->\n @if (loading && cardDatasource.length === 0) {\n <div class=\"osl-card-container my-2\" [style.height]=\"tableHeight\">\n <div class=\"osl-card-grid\">\n @for (sk of skeletonCardRows; track $index) {\n <div class=\"osl-card osl-card--skeleton\" [style.height.px]=\"cardHeightForVirtualscroll\">\n <div class=\"osl-card-inner\">\n <div class=\"osl-card-header\">\n <div class=\"osl-card-avatar osl-card-avatar--skeleton\"></div>\n <div style=\"flex:1;min-width:0\">\n <div class=\"osl-sk-line osl-sk-line--title\"></div>\n </div>\n </div>\n <div class=\"osl-card-divider\"></div>\n <div class=\"osl-card-body\">\n <div class=\"osl-card-field\"><div class=\"osl-sk-line osl-sk-line--label\"></div><div class=\"osl-sk-line\"></div></div>\n <div class=\"osl-card-field\"><div class=\"osl-sk-line osl-sk-line--label\"></div><div class=\"osl-sk-line osl-sk-line--short\"></div></div>\n <div class=\"osl-card-field\"><div class=\"osl-sk-line osl-sk-line--label\"></div><div class=\"osl-sk-line\"></div></div>\n </div>\n </div>\n </div>\n }\n </div>\n </div>\n }\n\n <!-- Empty state -->\n @else if (cardDatasource.length === 0 && !loading) {\n <div class=\"osl-card-container my-2\" [style.height]=\"tableHeight\">\n <div class=\"osl-card-empty\">\n <svg class=\"osl-card-empty-svg\" viewBox=\"0 0 180 160\" fill=\"none\">\n <ellipse cx=\"90\" cy=\"140\" rx=\"60\" ry=\"8\" fill=\"#f3f4f6\"/>\n <rect x=\"30\" y=\"30\" width=\"120\" height=\"96\" rx=\"10\" fill=\"#f9fafb\" stroke=\"#e5e7eb\" stroke-width=\"2\"/>\n <rect x=\"44\" y=\"48\" width=\"92\" height=\"10\" rx=\"5\" fill=\"#e5e7eb\"/>\n <rect x=\"44\" y=\"66\" width=\"72\" height=\"8\" rx=\"4\" fill=\"#f3f4f6\"/>\n <rect x=\"44\" y=\"82\" width=\"84\" height=\"8\" rx=\"4\" fill=\"#f3f4f6\"/>\n <rect x=\"44\" y=\"98\" width=\"56\" height=\"8\" rx=\"4\" fill=\"#f3f4f6\"/>\n <circle cx=\"140\" cy=\"108\" r=\"26\" fill=\"#fff\" stroke=\"#e5e7eb\" stroke-width=\"2\"/>\n <line x1=\"133\" y1=\"101\" x2=\"147\" y2=\"115\" stroke=\"#d1d5db\" stroke-width=\"2.5\" stroke-linecap=\"round\"/>\n <line x1=\"147\" y1=\"101\" x2=\"133\" y2=\"115\" stroke=\"#d1d5db\" stroke-width=\"2.5\" stroke-linecap=\"round\"/>\n </svg>\n <p class=\"osl-card-empty-title\">No records found</p>\n <p class=\"osl-card-empty-desc\">Try adjusting your search or filter criteria</p>\n </div>\n </div>\n }\n\n <!-- Virtual scroll card list -->\n @else {\n <div class=\"osl-card-vs-wrapper my-2\" [style.height]=\"tableHeight\">\n <cdk-virtual-scroll-viewport\n #cardViewport\n [itemSize]=\"cardHeightForVirtualscroll\"\n class=\"osl-card-virtual-viewport\"\n (scrolledIndexChange)=\"onVirtualScrollIndexChange($event)\">\n\n <div *cdkVirtualFor=\"let row of cardDatasource; trackBy: trackByCard; let i = index\"\n class=\"osl-card-vs-row\"\n [style.height.px]=\"cardHeightForVirtualscroll\">\n\n @if (cardTemplate) {\n <ng-container *ngTemplateOutlet=\"cardTemplate; context: { $implicit: row, index: i }\"></ng-container>\n } @else {\n <div class=\"osl-card\"\n [class.osl-card--highlighted]=\"isHighlightedCard(row)\"\n [class.osl-card--selectable]=\"isLister\"\n (click)=\"isLister ? onRowClick.emit(row) : null\">\n\n <div class=\"osl-card-inner\">\n\n <!-- Left: avatar + title -->\n <div class=\"osl-card-header\">\n <!-- @if (cardTitleColumn) {\n <div class=\"osl-card-avatar\">{{ getCardInitial(row) }}</div>\n } -->\n <div class=\"osl-card-title-wrap\">\n <span class=\"osl-card-title\">\n @if (cardTitleColumn) {\n @switch (cardTitleColumn.displayType) {\n @case ('date') { {{ row[cardTitleColumn.key] | date }} }\n @case ('datetime') { {{ row[cardTitleColumn.key] | date:'medium' }} }\n @case ('time') { {{ row[cardTitleColumn.key] | date:'shortTime' }} }\n @case ('customDateFormat') { {{ row[cardTitleColumn.key] | date:cardTitleColumn.customDateFormat }} }\n @default { {{ getCellValue(row, cardTitleColumn) }} }\n }\n }\n </span>\n </div>\n </div>\n\n <!-- Vertical divider -->\n <div class=\"osl-card-divider\"></div>\n\n <!-- Middle: body fields (horizontal) -->\n <div class=\"osl-card-body\">\n @for (col of cardBodyColumns; track col.key) {\n <div class=\"osl-card-field\">\n <span class=\"osl-card-label\">{{ col.label }}</span>\n <span class=\"osl-card-value\" [class.link]=\"col.displayType === 'link'\" (click)=\"col.click ? col.click(row, col) : null\">\n @switch (col.displayType) {\n @case ('date') { {{ row[col.key] | date }} }\n @case ('datetime') { {{ row[col.key] | date:'medium' }} }\n @case ('time') { {{ row[col.key] | date:'shortTime' }} }\n @case ('customDateFormat') { {{ row[col.key] | date:col.customDateFormat }} }\n @default { {{ getCellValue(row, col) }} }\n }\n </span>\n </div>\n }\n </div>\n\n <!-- Right: action buttons -->\n @if (!isLister && ((hasForm && (canEdit || canDelete)) || (moreMenuActions.length > 0 && hasVisibleActions(row)))) {\n <div class=\"osl-card-actions\">\n \n \n @if (hasForm && canDelete) {\n <button class=\"osl-card-action-btn osl-card-action-btn--delete\"\n (click)=\"$event.stopPropagation(); onDeleteClick(row)\" title=\"Delete\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polyline points=\"3 6 5 6 21 6\"/>\n <path d=\"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2\"/>\n </svg>\n </button>\n }\n </div>\n }\n @if (hasForm && canEdit) {\n <button class=\"osl-card-action-btn osl-card-action-btn--edit\"\n (click)=\"$event.stopPropagation(); openEditDialog(row)\" title=\"Edit\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7\"/>\n <path d=\"M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z\"/>\n </svg>\n </button>\n }\n\n @if (moreMenuActions.length > 0 && hasVisibleActions(row)) {\n <button class=\"osl-card-action-btn osl-card-action-btn--more\"\n (click)=\"toggleCardMenu(i, $event)\" title=\"More actions\">\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <circle cx=\"12\" cy=\"5\" r=\"2.2\"/>\n <circle cx=\"12\" cy=\"12\" r=\"2.2\"/>\n <circle cx=\"12\" cy=\"19\" r=\"2.2\"/>\n </svg>\n </button>\n }\n\n </div>\n </div>\n }\n </div>\n </cdk-virtual-scroll-viewport>\n </div>\n\n <!-- Infinite scroll loader (outside viewport) -->\n @if (loading) {\n <div class=\"osl-card-loader\">\n <div class=\"osl-card-loader-track\">\n <div class=\"osl-card-loader-bar\"></div>\n </div>\n <span class=\"osl-card-loader-text\">Loading more\u2026</span>\n </div>\n }\n\n <!-- All loaded footer (outside viewport) -->\n @if (allCardsLoaded && !loading && cardDatasource.length > 0) {\n <div class=\"osl-card-all-loaded\">\n <span class=\"osl-card-all-loaded-line\"></span>\n <span class=\"osl-card-all-loaded-text\">All {{ totalRecords || cardDatasource.length }} records loaded</span>\n <span class=\"osl-card-all-loaded-line\"></span>\n </div>\n }\n }\n }\n\n</div>\n\n<!-- Dialog: Form Body -->\n<ng-template #formBodyTpl>\n <osl-dynamic-form\n [skeletonLoading]=\"formLoading\"\n [elements]=\"formElements\"\n [(model)]=\"dialogModel\"\n ></osl-dynamic-form>\n</ng-template>\n\n<!-- Dialog: Form Footer -->\n<ng-template #formFooterTpl>\n <div class=\"osl-setup-dialog-footer\">\n <osl-button [loading]=\"saveLoading\" variant=\"secondary\" label=\"Save\" (click)=\"saveDialog()\"></osl-button>\n </div>\n</ng-template>\n\n<ng-template #customFooterWrapperTpl let-data>\n <ng-container *ngTemplateOutlet=\"customFormFooter!; context: { $implicit: { dialogModel: dialogModel, dialogMode: dialogMode, dialogRef: data.dialogRef } }\"></ng-container>\n</ng-template>\n\n<!-- Floating card more-actions menu \u2014 rendered outside card DOM to avoid transform containment -->\n@if (viewMode === 'card' && cardOpenMenuIndex !== null && moreMenuActions.length > 0) {\n <div class=\"osl-card-menu\"\n [style.top.px]=\"cardMenuPosition.top\"\n [style.left.px]=\"cardMenuPosition.left\"\n (click)=\"$event.stopPropagation()\">\n <div class=\"osl-card-menu-header\">Actions</div>\n @for (action of moreMenuActions; track $index) {\n @if (!action.hideIf || !action.hideIf(cardDatasource[cardOpenMenuIndex])) {\n <button class=\"osl-card-menu-item\"\n (click)=\"action.click(cardDatasource[cardOpenMenuIndex]); cardOpenMenuIndex = null\">\n <span class=\"osl-card-menu-dot\"></span>\n {{ action.labelIf ? action.labelIf(cardDatasource[cardOpenMenuIndex]) : action.label }}\n </button>\n }\n }\n </div>\n}\n", styles: [".osl-setup-header{display:flex;align-items:center;justify-content:space-between}.osl-setup-dialog-footer{display:flex;align-items:center;justify-content:flex-end;gap:10px;width:100%}.dialog-cancel-btn{display:flex;align-items:center;gap:6px;height:38px;font-size:13px;font-weight:500;border-color:var(--osl-border-color, #d1d5db);color:var(--osl-secondary, #6b7280);border-radius:var(--osl-border-radius, 4px);padding:0 16px;transition:all .2s ease}.dialog-cancel-btn mat-icon{font-size:17px;width:17px;height:17px}.dialog-cancel-btn:hover{border-color:#9ca3af;background:#f9fafb;color:#374151}.dialog-save-btn{display:flex;align-items:center;gap:6px;height:38px;font-size:13px;font-weight:500;background:linear-gradient(135deg,var(--osl-primary, #2563eb),#3b82f6);color:#fff;border-radius:var(--osl-border-radius, 4px);padding:0 18px;transition:all .2s ease;box-shadow:0 2px 8px #2563eb40}.dialog-save-btn mat-icon{font-size:17px;width:17px;height:17px}.dialog-save-btn:hover{background:linear-gradient(135deg,var(--osl-primary-hover, #1d4ed8),#2563eb);box-shadow:0 4px 14px #2563eb66;transform:translateY(-1px)}.dialog-save-btn:active{transform:translateY(0);box-shadow:0 2px 8px #2563eb40}.osl-view-toggle{display:flex;border:1.5px solid var(--osl-border-color, #e5e7eb);border-radius:8px;overflow:hidden;flex-shrink:0;background:#fff}.osl-view-toggle-btn{display:inline-flex;align-items:center;justify-content:center;width:34px;height:34px;border:none;background:transparent;cursor:pointer;color:#9ca3af;transition:background .15s,color .15s;padding:0}.osl-view-toggle-btn svg{width:16px;height:16px;transition:stroke .15s}.osl-view-toggle-btn:first-child{border-right:1.5px solid var(--osl-border-color, #e5e7eb)}.osl-view-toggle-btn--active{background:var(--osl-primary, #6366f1);color:#fff}.osl-view-toggle-btn--active svg{stroke:#fff}.osl-view-toggle-btn:not(.osl-view-toggle-btn--active):hover{background:#f3f4f6;color:#374151}.osl-card-container{overflow-y:auto;overflow-x:hidden;border-radius:12px;padding:4px 2px 16px;box-sizing:border-box}.osl-card-vs-wrapper{display:flex;flex-direction:column;overflow:hidden;box-sizing:border-box}.osl-card-virtual-viewport{flex:1;min-height:0;width:100%}.osl-card-vs-row{padding:0 2px 4px;box-sizing:border-box}.osl-card-vs-row>.osl-card{height:100%}.osl-card-grid{display:flex;flex-direction:column;gap:10px;padding:4px}@keyframes osl-card-enter{0%{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}.osl-card-grid .osl-card:nth-child(1){animation-delay:0s}.osl-card-grid .osl-card:nth-child(2){animation-delay:.03s}.osl-card-grid .osl-card:nth-child(3){animation-delay:.06s}.osl-card-grid .osl-card:nth-child(4){animation-delay:.09s}.osl-card-grid .osl-card:nth-child(5){animation-delay:.12s}.osl-card-grid .osl-card:nth-child(6){animation-delay:.15s}.osl-card-grid .osl-card:nth-child(7){animation-delay:.18s}.osl-card-grid .osl-card:nth-child(8){animation-delay:.21s}.osl-card{position:relative;background:#fff;border-radius:14px;border:1px solid #eaecf0;box-shadow:0 1px 4px #1018280f,0 1px 2px #1018280a;overflow:hidden;transition:transform .22s cubic-bezier(.34,1.56,.64,1),box-shadow .22s ease;animation:osl-card-enter .28s ease both}.osl-card:nth-child(6n+1){--card-accent: #6366f1;--card-accent-bg: #eef2ff}.osl-card:nth-child(6n+2){--card-accent: #0ea5e9;--card-accent-bg: #f0f9ff}.osl-card:nth-child(6n+3){--card-accent: #10b981;--card-accent-bg: #ecfdf5}.osl-card:nth-child(6n+4){--card-accent: #f59e0b;--card-accent-bg: #fffbeb}.osl-card:nth-child(6n+5){--card-accent: #f43f5e;--card-accent-bg: #fff1f2}.osl-card:nth-child(6n){--card-accent: #8b5cf6;--card-accent-bg: #faf5ff}.osl-card:before{content:\"\";position:absolute;top:0;left:0;bottom:0;width:4px;background:var(--card-accent, var(--osl-primary, #6366f1))}.osl-card:hover{transform:translateY(-2px);box-shadow:0 6px 20px #10182817,0 2px 8px #1018280d;border-color:#d0d5dd}.osl-card--highlighted{outline:2px solid var(--osl-primary, #6366f1);outline-offset:2px;animation:osl-card-enter .28s ease both,osl-card-highlight-pulse 2.5s ease-out .3s both}.osl-card--selectable{cursor:pointer}.osl-card--skeleton{pointer-events:none;animation:osl-card-enter .28s ease both}.osl-card--skeleton:hover{transform:none;box-shadow:0 1px 4px #1018280f}@keyframes osl-card-highlight-pulse{0%{box-shadow:0 0 0 4px #6366f14d}to{box-shadow:none}}.osl-card-inner{padding:12px 16px 12px 20px;display:flex;flex-direction:row;align-items:center;gap:0;min-height:0}.osl-card-header{display:flex;align-items:center;gap:12px;flex-shrink:0;width:220px;min-width:0;padding-right:16px}.osl-card-avatar{width:40px;height:40px;border-radius:10px;background:var(--card-accent, var(--osl-primary, #6366f1));color:#fff;font-size:16px;font-weight:700;display:flex;align-items:center;justify-content:center;flex-shrink:0;letter-spacing:0;box-shadow:0 4px 10px #00000026}.osl-card-avatar--skeleton{background:#e5e7eb;box-shadow:none;animation:osl-sk-pulse 1.4s ease-in-out infinite}.osl-card-title-wrap{flex:1;min-width:0}.osl-card-title{display:block;font-weight:700;font-size:14px;color:#101828;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;line-height:1.4}.osl-card-actions{display:flex;align-items:center;gap:6px;flex-shrink:0;margin-left:12px}.osl-card-action-btn{display:inline-flex;align-items:center;justify-content:center;width:30px;height:30px;border-radius:8px;border:1.5px solid #e4e7ec;background:#fff;cursor:pointer;transition:background .15s,border-color .15s,transform .15s,box-shadow .15s;padding:0}.osl-card-action-btn svg{width:14px;height:14px;transition:stroke .15s,fill .15s}.osl-card-action-btn--more svg{fill:#9ca3af;stroke:none}.osl-card-action-btn--more:hover{background:var(--card-accent-bg, #eef2ff);border-color:var(--card-accent, var(--osl-primary, #6366f1));box-shadow:0 2px 6px #00000014;transform:scale(1.05)}.osl-card-action-btn--more:hover svg{fill:var(--card-accent, var(--osl-primary, #6366f1))}.osl-card-action-btn--edit svg{stroke:#6b7280}.osl-card-action-btn--edit:hover{background:var(--card-accent-bg, #eef2ff);border-color:var(--card-accent, var(--osl-primary, #6366f1));box-shadow:0 2px 6px #00000014;transform:scale(1.05)}.osl-card-action-btn--edit:hover svg{stroke:var(--card-accent, var(--osl-primary, #6366f1))}.osl-card-action-btn--delete svg{stroke:#9ca3af}.osl-card-action-btn--delete:hover{background:#fff1f2;border-color:#f43f5e;box-shadow:0 2px 6px #f43f5e26;transform:scale(1.05)}.osl-card-action-btn--delete:hover svg{stroke:#f43f5e}.osl-card-divider{width:1px;align-self:stretch;background:#f2f4f7;margin:0 16px;flex-shrink:0}.osl-card-body{display:flex;flex-direction:row;align-items:center;gap:24px;flex:1;min-width:0;flex-wrap:wrap}.osl-card-field{display:flex;flex-direction:column;gap:3px;flex:1;min-width:100px;max-width:200px}.osl-card-label{font-size:10px;font-weight:600;color:#9ca3af;text-transform:uppercase;letter-spacing:.06em;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.osl-card-value{font-size:13px;font-weight:500;color:#374151;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}.osl-card-value.link{text-decoration:underline;color:#2563eb;cursor:pointer}.osl-card-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:320px;gap:12px;padding:40px 20px}.osl-card-empty-svg{width:180px;height:auto;opacity:.85}.osl-card-empty-title{font-size:16px;font-weight:600;color:#374151;margin:4px 0 0;text-align:center}.osl-card-empty-desc{font-size:13px;color:#9ca3af;margin:0;text-align:center}.osl-card-loader{display:flex;flex-direction:column;align-items:center;gap:10px;padding:28px 20px 16px}.osl-card-loader-track{width:160px;height:3px;background:#f3f4f6;border-radius:99px;overflow:hidden}.osl-card-loader-bar{height:100%;width:40%;background:linear-gradient(90deg,transparent,var(--osl-primary, #6366f1),transparent);border-radius:99px;animation:osl-loader-sweep 1.2s ease-in-out infinite}@keyframes osl-loader-sweep{0%{transform:translate(-200%)}to{transform:translate(500%)}}.osl-card-loader-text{font-size:12px;color:#9ca3af;font-weight:500}.osl-card-all-loaded{display:flex;align-items:center;gap:12px;padding:20px 16px 8px;justify-content:center}.osl-card-all-loaded-line{flex:1;max-width:80px;height:1px;background:#e5e7eb}.osl-card-all-loaded-text{font-size:11px;color:#d1d5db;font-weight:600;text-transform:uppercase;letter-spacing:.06em;white-space:nowrap}@keyframes osl-sk-pulse{0%,to{opacity:1}50%{opacity:.4}}.osl-sk-line{height:12px;background:#e9eaec;border-radius:6px;animation:osl-sk-pulse 1.4s ease-in-out infinite;width:80%}.osl-sk-line--title{height:14px;width:65%}.osl-sk-line--label{width:40%;height:10px}.osl-sk-line--short{width:50%}.osl-card-menu{position:fixed;z-index:9999;min-width:196px;background:#fff;border:1px solid #eaecf0;border-radius:12px;box-shadow:0 12px 32px #10182824,0 4px 12px #1018280f;overflow:hidden;animation:osl-menu-pop .16s cubic-bezier(.16,1,.3,1)}@keyframes osl-menu-pop{0%{opacity:0;transform:translateY(-8px) scale(.96)}to{opacity:1;transform:translateY(0) scale(1)}}.osl-card-menu-header{padding:10px 14px 7px;font-size:10px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:#9ca3af;border-bottom:1px solid #f2f4f7;-webkit-user-select:none;user-select:none}.osl-card-menu-item{display:flex;align-items:center;gap:10px;width:100%;padding:10px 14px;background:transparent;border:none;border-left:3px solid transparent;border-bottom:1px solid #f9fafb;text-align:left;font-size:13px;font-weight:500;font-family:inherit;color:#374151;cursor:pointer;white-space:nowrap;transition:background .12s,color .12s,border-left-color .12s}.osl-card-menu-item:last-child{border-bottom:none}.osl-card-menu-item:hover{background:var(--card-accent-bg, #f5f3ff);color:var(--card-accent, var(--osl-primary, #6366f1));border-left-color:var(--card-accent, var(--osl-primary, #6366f1))}.osl-card-menu-item:hover .osl-card-menu-dot{background:var(--card-accent, var(--osl-primary, #6366f1));box-shadow:0 0 0 3px #6366f129}.osl-card-menu-dot{flex-shrink:0;width:6px;height:6px;border-radius:50%;background:#d1d5db;transition:background .12s,box-shadow .12s}\n"], dependencies: [{ kind: "directive", type: i1$2.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i2$2.CdkFixedSizeVirtualScroll, selector: "cdk-virtual-scroll-viewport[itemSize]", inputs: ["itemSize", "minBufferPx", "maxBufferPx"] }, { kind: "directive", type: i2$2.CdkVirtualForOf, selector: "[cdkVirtualFor][cdkVirtualForOf]", inputs: ["cdkVirtualForOf", "cdkVirtualForTrackBy", "cdkVirtualForTemplate", "cdkVirtualForTemplateCacheSize"] }, { kind: "component", type: i2$2.CdkVirtualScrollViewport, selector: "cdk-virtual-scroll-viewport", inputs: ["orientation", "appendOnly"], outputs: ["scrolledIndexChange"] }, { kind: "component", type: DynamicForm, selector: "osl-dynamic-form", inputs: ["elements", "model", "skeletonLoading", "skeletonTheme"], outputs: ["modelChange"] }, { kind: "component", type: OslButton, selector: "osl-button", inputs: ["label", "icon", "variant", "size", "disabled", "loading", "type", "fullWidth"], outputs: ["clickEv"] }, { kind: "component", type: OslSearchbar, selector: "osl-searchbar", inputs: ["label"], outputs: ["onSearch"] }, { kind: "component", type: OslGrid, selector: "osl-grid", inputs: ["columns", "datasource", "isPaginated", "pageSize", "autoMode", "totalRecords", "tableHeight", "loading", "isSelectable", "moreMenuActions", "canEdit", "canDelete", "highlightedRow", "primaryKey"], outputs: ["datasourceChange", "pageChange", "pageSizeChange", "sortChange", "editClick", "deleteClick", "onRowClick"] }, { kind: "pipe", type: i1$2.DatePipe, name: "date" }] });
|
|
3851
3877
|
}
|
|
3852
3878
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: OslSetup, decorators: [{
|
|
3853
3879
|
type: Component,
|
|
3854
|
-
args: [{ selector: 'osl-setup', standalone: false, template: "<div class=\"p-2\">\n\n <!-- \u2500\u2500 Header \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div class=\"osl-setup-header\">\n <h5 class=\"mb-0\">{{ title }}</h5>\n <div class=\"d-flex align-items-center gap-2\">\n <osl-searchbar #searchbar class=\"mx-2\" (onSearch)=\"onSearchSetup($event)\"></osl-searchbar>\n\n <!-- View Toggle -->\n @if(!isLister){\n <div class=\"osl-view-toggle\">\n <button class=\"osl-view-toggle-btn\" [class.osl-view-toggle-btn--active]=\"viewMode === 'table'\"\n (click)=\"toggleView('table')\" title=\"Table view\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"2\"/>\n <line x1=\"3\" y1=\"9\" x2=\"21\" y2=\"9\"/>\n <line x1=\"3\" y1=\"15\" x2=\"21\" y2=\"15\"/>\n <line x1=\"9\" y1=\"9\" x2=\"9\" y2=\"21\"/>\n </svg>\n </button>\n <button class=\"osl-view-toggle-btn\" [class.osl-view-toggle-btn--active]=\"viewMode === 'card'\"\n (click)=\"toggleView('card')\" title=\"Card view\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <rect x=\"3\" y=\"3\" width=\"7\" height=\"7\" rx=\"1.5\"/>\n <rect x=\"14\" y=\"3\" width=\"7\" height=\"7\" rx=\"1.5\"/>\n <rect x=\"3\" y=\"14\" width=\"7\" height=\"7\" rx=\"1.5\"/>\n <rect x=\"14\" y=\"14\" width=\"7\" height=\"7\" rx=\"1.5\"/>\n </svg>\n </button>\n </div>\n }\n\n @if (!isLister && canAdd) {\n <osl-button variant=\"secondary\" size=\"sm\" [label]=\"'Add ' + title\" (clickEv)=\"openAddDialog()\"></osl-button>\n }\n </div>\n </div>\n\n <!-- \u2500\u2500 Table View \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (viewMode === 'table') {\n <div class=\"osl-setup-body my-2\">\n <osl-grid\n #gridRef\n [columns]=\"columnsWithActions\"\n [(datasource)]=\"datasource\"\n [isPaginated]=\"isPaginated\"\n [pageSize]=\"pageSize\"\n [autoMode]=\"autoMode\"\n [tableHeight]=\"tableHeight\"\n [totalRecords]=\"totalRecords\"\n [loading]=\"loading\"\n [moreMenuActions]=\"moreMenuActions\"\n [canEdit]=\"canEdit\"\n [canDelete]=\"canDelete\"\n [highlightedRow]=\"restoredRow\"\n [primaryKey]=\"primaryKey\"\n (editClick)=\"openEditDialog($event)\"\n (deleteClick)=\"onDeleteClick($event)\"\n (pageChange)=\"onPageChange(pageChange, $event)\"\n (pageSizeChange)=\"onPageChange(pageSizeChange, $event)\"\n (sortChange)=\"sortChange.emit($event)\"\n [isSelectable]=\"isLister\"\n (onRowClick)=\"onRowClick.emit($event)\"\n />\n </div>\n }\n\n <!-- \u2500\u2500 Card View \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (viewMode === 'card') {\n <div #cardContainerRef class=\"osl-card-container my-2\" [style.height]=\"tableHeight\" (scroll)=\"onCardScroll($event)\">\n\n <!-- Skeleton cards on initial load -->\n @if (loading && cardDatasource.length === 0) {\n <div class=\"osl-card-grid\">\n @for (sk of skeletonCardRows; track $index) {\n <div class=\"osl-card osl-card--skeleton\">\n <div class=\"osl-card-inner\">\n <div class=\"osl-card-header\">\n <div class=\"osl-card-avatar osl-card-avatar--skeleton\"></div>\n <div style=\"flex:1\">\n <div class=\"osl-sk-line osl-sk-line--title\"></div>\n </div>\n </div>\n <div class=\"osl-card-divider\"></div>\n <div class=\"osl-card-body\">\n <div class=\"osl-card-field\"><div class=\"osl-sk-line osl-sk-line--label\"></div><div class=\"osl-sk-line\"></div></div>\n <div class=\"osl-card-field\"><div class=\"osl-sk-line osl-sk-line--label\"></div><div class=\"osl-sk-line osl-sk-line--short\"></div></div>\n <div class=\"osl-card-field\"><div class=\"osl-sk-line osl-sk-line--label\"></div><div class=\"osl-sk-line\"></div></div>\n </div>\n </div>\n </div>\n }\n </div>\n }\n\n <!-- Empty state -->\n @else if (cardDatasource.length === 0 && !loading) {\n <div class=\"osl-card-empty\">\n <svg class=\"osl-card-empty-svg\" viewBox=\"0 0 180 160\" fill=\"none\">\n <ellipse cx=\"90\" cy=\"140\" rx=\"60\" ry=\"8\" fill=\"#f3f4f6\"/>\n <rect x=\"30\" y=\"30\" width=\"120\" height=\"96\" rx=\"10\" fill=\"#f9fafb\" stroke=\"#e5e7eb\" stroke-width=\"2\"/>\n <rect x=\"44\" y=\"48\" width=\"92\" height=\"10\" rx=\"5\" fill=\"#e5e7eb\"/>\n <rect x=\"44\" y=\"66\" width=\"72\" height=\"8\" rx=\"4\" fill=\"#f3f4f6\"/>\n <rect x=\"44\" y=\"82\" width=\"84\" height=\"8\" rx=\"4\" fill=\"#f3f4f6\"/>\n <rect x=\"44\" y=\"98\" width=\"56\" height=\"8\" rx=\"4\" fill=\"#f3f4f6\"/>\n <circle cx=\"140\" cy=\"108\" r=\"26\" fill=\"#fff\" stroke=\"#e5e7eb\" stroke-width=\"2\"/>\n <line x1=\"133\" y1=\"101\" x2=\"147\" y2=\"115\" stroke=\"#d1d5db\" stroke-width=\"2.5\" stroke-linecap=\"round\"/>\n <line x1=\"147\" y1=\"101\" x2=\"133\" y2=\"115\" stroke=\"#d1d5db\" stroke-width=\"2.5\" stroke-linecap=\"round\"/>\n </svg>\n <p class=\"osl-card-empty-title\">No records found</p>\n <p class=\"osl-card-empty-desc\">Try adjusting your search or filter criteria</p>\n </div>\n }\n\n <!-- Card grid -->\n @else {\n <div class=\"osl-card-grid\">\n\n @if (cardTemplate) {\n @for (row of cardDatasource; track row[primaryKey]; let i = $index) {\n <ng-container *ngTemplateOutlet=\"cardTemplate; context: { $implicit: row, index: i }\"></ng-container>\n }\n } @else {\n @for (row of cardDatasource; track row[primaryKey]; let i = $index) {\n <div class=\"osl-card\"\n [class.osl-card--highlighted]=\"isHighlightedCard(row)\"\n [class.osl-card--selectable]=\"isLister\"\n (click)=\"isLister ? onRowClick.emit(row) : null\">\n\n <div class=\"osl-card-inner\">\n\n <!-- Card header -->\n <div class=\"osl-card-header\">\n <!-- Avatar circle -->\n @if (cardTitleColumn) {\n <div class=\"osl-card-avatar\">{{ getCardInitial(row) }}</div>\n }\n\n <!-- Title -->\n <div class=\"osl-card-title-wrap\">\n <span class=\"osl-card-title\">\n @if (cardTitleColumn) {\n @switch (cardTitleColumn.displayType) {\n @case ('date') { {{ row[cardTitleColumn.key] | date }} }\n @case ('datetime') { {{ row[cardTitleColumn.key] | date:'medium' }} }\n @case ('time') { {{ row[cardTitleColumn.key] | date:'shortTime' }} }\n @case ('customDateFormat') { {{ row[cardTitleColumn.key] | date:cardTitleColumn.customDateFormat }} }\n @default { {{ getCellValue(row, cardTitleColumn) }} }\n }\n }\n </span>\n </div>\n\n <!-- Action buttons -->\n @if (!isLister && ((hasForm && (canEdit || canDelete)) || (moreMenuActions.length > 0 && hasVisibleActions(row)))) {\n <div class=\"osl-card-actions\">\n\n <!-- More-actions trigger (menu rendered at root to avoid transform containment) -->\n @if (moreMenuActions.length > 0 && hasVisibleActions(row)) {\n <button class=\"osl-card-action-btn osl-card-action-btn--more\"\n (click)=\"toggleCardMenu(i, $event)\" title=\"More actions\">\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <circle cx=\"12\" cy=\"5\" r=\"2.2\"/>\n <circle cx=\"12\" cy=\"12\" r=\"2.2\"/>\n <circle cx=\"12\" cy=\"19\" r=\"2.2\"/>\n </svg>\n </button>\n }\n\n <!-- Edit -->\n @if (hasForm && canEdit) {\n <button class=\"osl-card-action-btn osl-card-action-btn--edit\"\n (click)=\"$event.stopPropagation(); openEditDialog(row)\" title=\"Edit\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7\"/>\n <path d=\"M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z\"/>\n </svg>\n </button>\n }\n\n <!-- Delete -->\n @if (hasForm && canDelete) {\n <button class=\"osl-card-action-btn osl-card-action-btn--delete\"\n (click)=\"$event.stopPropagation(); onDeleteClick(row)\" title=\"Delete\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polyline points=\"3 6 5 6 21 6\"/>\n <path d=\"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2\"/>\n </svg>\n </button>\n }\n </div>\n }\n </div>\n\n <!-- Divider -->\n <div class=\"osl-card-divider\"></div>\n\n <!-- Card body: remaining fields -->\n <div class=\"osl-card-body\">\n @for (col of cardBodyColumns; track col.key) {\n <div class=\"osl-card-field\">\n <span class=\"osl-card-label\">{{ col.label }}</span>\n <span class=\"osl-card-value\" [class.link]=\"col.displayType === 'link'\" (click)=\"col.click ? col.click(row, col) : null\">\n @switch (col.displayType) {\n @case ('date') { {{ row[col.key] | date }} }\n @case ('datetime') { {{ row[col.key] | date:'medium' }} }\n @case ('time') { {{ row[col.key] | date:'shortTime' }} }\n @case ('customDateFormat') { {{ row[col.key] | date:col.customDateFormat }} }\n\n @default { {{ getCellValue(row, col) }} }\n }\n </span>\n </div>\n }\n </div>\n\n </div>\n </div>\n }\n }\n </div>\n\n <!-- Infinite scroll bottom loader -->\n @if (loading) {\n <div class=\"osl-card-loader\">\n <div class=\"osl-card-loader-track\">\n <div class=\"osl-card-loader-bar\"></div>\n </div>\n <span class=\"osl-card-loader-text\">Loading more\u2026</span>\n </div>\n }\n\n <!-- All loaded footer -->\n @if (allCardsLoaded && !loading && cardDatasource.length > 0) {\n <div class=\"osl-card-all-loaded\">\n <span class=\"osl-card-all-loaded-line\"></span>\n <span class=\"osl-card-all-loaded-text\">All {{ totalRecords || cardDatasource.length }} records loaded</span>\n <span class=\"osl-card-all-loaded-line\"></span>\n </div>\n }\n }\n </div>\n }\n\n</div>\n\n<!-- Dialog: Form Body -->\n<ng-template #formBodyTpl>\n <osl-dynamic-form\n [skeletonLoading]=\"formLoading\"\n [elements]=\"formElements\"\n [(model)]=\"dialogModel\"\n ></osl-dynamic-form>\n</ng-template>\n\n<!-- Dialog: Form Footer -->\n<ng-template #formFooterTpl>\n <div class=\"osl-setup-dialog-footer\">\n <osl-button [loading]=\"saveLoading\" variant=\"secondary\" label=\"Save\" (click)=\"saveDialog()\"></osl-button>\n </div>\n</ng-template>\n\n<ng-template #customFooterWrapperTpl let-data>\n <ng-container *ngTemplateOutlet=\"customFormFooter!; context: { $implicit: { dialogModel: dialogModel, dialogMode: dialogMode, dialogRef: data.dialogRef } }\"></ng-container>\n</ng-template>\n\n<!-- Floating card more-actions menu \u2014 rendered outside card DOM to avoid transform containment -->\n@if (viewMode === 'card' && cardOpenMenuIndex !== null && moreMenuActions.length > 0) {\n <div class=\"osl-card-menu\"\n [style.top.px]=\"cardMenuPosition.top\"\n [style.left.px]=\"cardMenuPosition.left\"\n (click)=\"$event.stopPropagation()\">\n <div class=\"osl-card-menu-header\">Actions</div>\n @for (action of moreMenuActions; track $index) {\n @if (!action.hideIf || !action.hideIf(cardDatasource[cardOpenMenuIndex])) {\n <button class=\"osl-card-menu-item\"\n (click)=\"action.click(cardDatasource[cardOpenMenuIndex]); cardOpenMenuIndex = null\">\n <span class=\"osl-card-menu-dot\"></span>\n {{ action.labelIf ? action.labelIf(cardDatasource[cardOpenMenuIndex]) : action.label }}\n </button>\n }\n }\n </div>\n}\n", styles: [".osl-setup-header{display:flex;align-items:center;justify-content:space-between}.osl-setup-dialog-footer{display:flex;align-items:center;justify-content:flex-end;gap:10px;width:100%}.dialog-cancel-btn{display:flex;align-items:center;gap:6px;height:38px;font-size:13px;font-weight:500;border-color:var(--osl-border-color, #d1d5db);color:var(--osl-secondary, #6b7280);border-radius:var(--osl-border-radius, 4px);padding:0 16px;transition:all .2s ease}.dialog-cancel-btn mat-icon{font-size:17px;width:17px;height:17px}.dialog-cancel-btn:hover{border-color:#9ca3af;background:#f9fafb;color:#374151}.dialog-save-btn{display:flex;align-items:center;gap:6px;height:38px;font-size:13px;font-weight:500;background:linear-gradient(135deg,var(--osl-primary, #2563eb),#3b82f6);color:#fff;border-radius:var(--osl-border-radius, 4px);padding:0 18px;transition:all .2s ease;box-shadow:0 2px 8px #2563eb40}.dialog-save-btn mat-icon{font-size:17px;width:17px;height:17px}.dialog-save-btn:hover{background:linear-gradient(135deg,var(--osl-primary-hover, #1d4ed8),#2563eb);box-shadow:0 4px 14px #2563eb66;transform:translateY(-1px)}.dialog-save-btn:active{transform:translateY(0);box-shadow:0 2px 8px #2563eb40}.osl-view-toggle{display:flex;border:1.5px solid var(--osl-border-color, #e5e7eb);border-radius:8px;overflow:hidden;flex-shrink:0;background:#fff}.osl-view-toggle-btn{display:inline-flex;align-items:center;justify-content:center;width:34px;height:34px;border:none;background:transparent;cursor:pointer;color:#9ca3af;transition:background .15s,color .15s;padding:0}.osl-view-toggle-btn svg{width:16px;height:16px;transition:stroke .15s}.osl-view-toggle-btn:first-child{border-right:1.5px solid var(--osl-border-color, #e5e7eb)}.osl-view-toggle-btn--active{background:var(--osl-primary, #6366f1);color:#fff}.osl-view-toggle-btn--active svg{stroke:#fff}.osl-view-toggle-btn:not(.osl-view-toggle-btn--active):hover{background:#f3f4f6;color:#374151}.osl-card-container{overflow-y:auto;overflow-x:hidden;border-radius:12px;padding:4px 2px 16px;box-sizing:border-box}.osl-card-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:16px;padding:4px}@keyframes osl-card-enter{0%{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}.osl-card-grid .osl-card:nth-child(1){animation-delay:0s}.osl-card-grid .osl-card:nth-child(2){animation-delay:.04s}.osl-card-grid .osl-card:nth-child(3){animation-delay:.08s}.osl-card-grid .osl-card:nth-child(4){animation-delay:.12s}.osl-card-grid .osl-card:nth-child(5){animation-delay:.16s}.osl-card-grid .osl-card:nth-child(6){animation-delay:.2s}.osl-card-grid .osl-card:nth-child(7){animation-delay:.24s}.osl-card-grid .osl-card:nth-child(8){animation-delay:.28s}.osl-card-grid .osl-card:nth-child(9){animation-delay:.32s}.osl-card-grid .osl-card:nth-child(10){animation-delay:.36s}.osl-card-grid .osl-card:nth-child(11){animation-delay:.4s}.osl-card-grid .osl-card:nth-child(12){animation-delay:.44s}.osl-card{position:relative;background:#fff;border-radius:14px;border:1px solid #eaecf0;box-shadow:0 1px 4px #1018280f,0 1px 2px #1018280a;overflow:hidden;transition:transform .22s cubic-bezier(.34,1.56,.64,1),box-shadow .22s ease;animation:osl-card-enter .28s ease both}.osl-card:nth-child(6n+1){--card-accent: #6366f1;--card-accent-bg: #eef2ff}.osl-card:nth-child(6n+2){--card-accent: #0ea5e9;--card-accent-bg: #f0f9ff}.osl-card:nth-child(6n+3){--card-accent: #10b981;--card-accent-bg: #ecfdf5}.osl-card:nth-child(6n+4){--card-accent: #f59e0b;--card-accent-bg: #fffbeb}.osl-card:nth-child(6n+5){--card-accent: #f43f5e;--card-accent-bg: #fff1f2}.osl-card:nth-child(6n){--card-accent: #8b5cf6;--card-accent-bg: #faf5ff}.osl-card:before{content:\"\";position:absolute;top:0;left:0;bottom:0;width:4px;background:var(--card-accent, var(--osl-primary, #6366f1))}.osl-card:hover{transform:translateY(-4px);box-shadow:0 12px 32px #1018281a,0 4px 12px #1018280f;border-color:#d0d5dd}.osl-card--highlighted{outline:2px solid var(--osl-primary, #6366f1);outline-offset:2px;animation:osl-card-enter .28s ease both,osl-card-highlight-pulse 2.5s ease-out .3s both}.osl-card--selectable{cursor:pointer}.osl-card--skeleton{pointer-events:none;animation:osl-card-enter .28s ease both}.osl-card--skeleton:hover{transform:none;box-shadow:0 1px 4px #1018280f}@keyframes osl-card-highlight-pulse{0%{box-shadow:0 0 0 4px #6366f14d}to{box-shadow:none}}.osl-card-inner{padding:16px 16px 16px 20px;display:flex;flex-direction:column;height:100%}.osl-card-header{display:flex;align-items:center;gap:12px;margin-bottom:0}.osl-card-avatar{width:40px;height:40px;border-radius:10px;background:var(--card-accent, var(--osl-primary, #6366f1));color:#fff;font-size:16px;font-weight:700;display:flex;align-items:center;justify-content:center;flex-shrink:0;letter-spacing:0;box-shadow:0 4px 10px #00000026}.osl-card-avatar--skeleton{background:#e5e7eb;box-shadow:none;animation:osl-sk-pulse 1.4s ease-in-out infinite}.osl-card-title-wrap{flex:1;min-width:0}.osl-card-title{display:block;font-weight:700;font-size:14px;color:#101828;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;line-height:1.4}.osl-card-actions{display:flex;align-items:center;gap:6px;flex-shrink:0}.osl-card-action-btn{display:inline-flex;align-items:center;justify-content:center;width:30px;height:30px;border-radius:8px;border:1.5px solid #e4e7ec;background:#fff;cursor:pointer;transition:background .15s,border-color .15s,transform .15s,box-shadow .15s;padding:0}.osl-card-action-btn svg{width:14px;height:14px;transition:stroke .15s,fill .15s}.osl-card-action-btn--more svg{fill:#9ca3af;stroke:none}.osl-card-action-btn--more:hover{background:var(--card-accent-bg, #eef2ff);border-color:var(--card-accent, var(--osl-primary, #6366f1));box-shadow:0 2px 6px #00000014;transform:scale(1.05)}.osl-card-action-btn--more:hover svg{fill:var(--card-accent, var(--osl-primary, #6366f1))}.osl-card-action-btn--edit svg{stroke:#6b7280}.osl-card-action-btn--edit:hover{background:var(--card-accent-bg, #eef2ff);border-color:var(--card-accent, var(--osl-primary, #6366f1));box-shadow:0 2px 6px #00000014;transform:scale(1.05)}.osl-card-action-btn--edit:hover svg{stroke:var(--card-accent, var(--osl-primary, #6366f1))}.osl-card-action-btn--delete svg{stroke:#9ca3af}.osl-card-action-btn--delete:hover{background:#fff1f2;border-color:#f43f5e;box-shadow:0 2px 6px #f43f5e26;transform:scale(1.05)}.osl-card-action-btn--delete:hover svg{stroke:#f43f5e}.osl-card-divider{height:1px;background:#f2f4f7;margin:14px 0}.osl-card-body{display:flex;flex-direction:column;gap:10px;flex:1}.osl-card-field{display:grid;grid-template-columns:100px 1fr;gap:6px;align-items:baseline}.osl-card-label{font-size:10px;font-weight:600;color:#9ca3af;text-transform:uppercase;letter-spacing:.06em;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.osl-card-value{font-size:13px;font-weight:500;color:#374151;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}.osl-card-value.link{text-decoration:underline;color:#2563eb;cursor:pointer}.osl-card-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:320px;gap:12px;padding:40px 20px}.osl-card-empty-svg{width:180px;height:auto;opacity:.85}.osl-card-empty-title{font-size:16px;font-weight:600;color:#374151;margin:4px 0 0;text-align:center}.osl-card-empty-desc{font-size:13px;color:#9ca3af;margin:0;text-align:center}.osl-card-loader{display:flex;flex-direction:column;align-items:center;gap:10px;padding:28px 20px 16px}.osl-card-loader-track{width:160px;height:3px;background:#f3f4f6;border-radius:99px;overflow:hidden}.osl-card-loader-bar{height:100%;width:40%;background:linear-gradient(90deg,transparent,var(--osl-primary, #6366f1),transparent);border-radius:99px;animation:osl-loader-sweep 1.2s ease-in-out infinite}@keyframes osl-loader-sweep{0%{transform:translate(-200%)}to{transform:translate(500%)}}.osl-card-loader-text{font-size:12px;color:#9ca3af;font-weight:500}.osl-card-all-loaded{display:flex;align-items:center;gap:12px;padding:20px 16px 8px;justify-content:center}.osl-card-all-loaded-line{flex:1;max-width:80px;height:1px;background:#e5e7eb}.osl-card-all-loaded-text{font-size:11px;color:#d1d5db;font-weight:600;text-transform:uppercase;letter-spacing:.06em;white-space:nowrap}@keyframes osl-sk-pulse{0%,to{opacity:1}50%{opacity:.4}}.osl-sk-line{height:12px;background:#e9eaec;border-radius:6px;animation:osl-sk-pulse 1.4s ease-in-out infinite;width:80%}.osl-sk-line--title{height:14px;width:65%}.osl-sk-line--label{width:40%;height:10px}.osl-sk-line--short{width:50%}.osl-card-menu{position:fixed;z-index:9999;min-width:196px;background:#fff;border:1px solid #eaecf0;border-radius:12px;box-shadow:0 12px 32px #10182824,0 4px 12px #1018280f;overflow:hidden;animation:osl-menu-pop .16s cubic-bezier(.16,1,.3,1)}@keyframes osl-menu-pop{0%{opacity:0;transform:translateY(-8px) scale(.96)}to{opacity:1;transform:translateY(0) scale(1)}}.osl-card-menu-header{padding:10px 14px 7px;font-size:10px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:#9ca3af;border-bottom:1px solid #f2f4f7;-webkit-user-select:none;user-select:none}.osl-card-menu-item{display:flex;align-items:center;gap:10px;width:100%;padding:10px 14px;background:transparent;border:none;border-left:3px solid transparent;border-bottom:1px solid #f9fafb;text-align:left;font-size:13px;font-weight:500;font-family:inherit;color:#374151;cursor:pointer;white-space:nowrap;transition:background .12s,color .12s,border-left-color .12s}.osl-card-menu-item:last-child{border-bottom:none}.osl-card-menu-item:hover{background:var(--card-accent-bg, #f5f3ff);color:var(--card-accent, var(--osl-primary, #6366f1));border-left-color:var(--card-accent, var(--osl-primary, #6366f1))}.osl-card-menu-item:hover .osl-card-menu-dot{background:var(--card-accent, var(--osl-primary, #6366f1));box-shadow:0 0 0 3px #6366f129}.osl-card-menu-dot{flex-shrink:0;width:6px;height:6px;border-radius:50%;background:#d1d5db;transition:background .12s,box-shadow .12s}\n"] }]
|
|
3880
|
+
args: [{ selector: 'osl-setup', standalone: false, template: "<div class=\"p-2\">\n\n <!-- \u2500\u2500 Header \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div class=\"osl-setup-header\">\n <h5 class=\"mb-0\">{{ title }}</h5>\n <div class=\"d-flex align-items-center gap-2\">\n <osl-searchbar #searchbar class=\"mx-2\" (onSearch)=\"onSearchSetup($event)\"></osl-searchbar>\n\n <!-- View Toggle -->\n @if(!isLister){\n <div class=\"osl-view-toggle\">\n <button class=\"osl-view-toggle-btn\" [class.osl-view-toggle-btn--active]=\"viewMode === 'table'\"\n (click)=\"toggleView('table')\" title=\"Table view\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"2\"/>\n <line x1=\"3\" y1=\"9\" x2=\"21\" y2=\"9\"/>\n <line x1=\"3\" y1=\"15\" x2=\"21\" y2=\"15\"/>\n <line x1=\"9\" y1=\"9\" x2=\"9\" y2=\"21\"/>\n </svg>\n </button>\n <button class=\"osl-view-toggle-btn\" [class.osl-view-toggle-btn--active]=\"viewMode === 'card'\"\n (click)=\"toggleView('card')\" title=\"Card view\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <rect x=\"3\" y=\"3\" width=\"7\" height=\"7\" rx=\"1.5\"/>\n <rect x=\"14\" y=\"3\" width=\"7\" height=\"7\" rx=\"1.5\"/>\n <rect x=\"3\" y=\"14\" width=\"7\" height=\"7\" rx=\"1.5\"/>\n <rect x=\"14\" y=\"14\" width=\"7\" height=\"7\" rx=\"1.5\"/>\n </svg>\n </button>\n </div>\n }\n\n @if (!isLister && canAdd) {\n <osl-button variant=\"secondary\" size=\"sm\" [label]=\"'Add ' + title\" (clickEv)=\"openAddDialog()\"></osl-button>\n }\n </div>\n </div>\n\n <!-- \u2500\u2500 Table View \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (viewMode === 'table') {\n <div class=\"osl-setup-body my-2\">\n <osl-grid\n #gridRef\n [columns]=\"columnsWithActions\"\n [(datasource)]=\"datasource\"\n [isPaginated]=\"isPaginated\"\n [pageSize]=\"pageSize\"\n [autoMode]=\"autoMode\"\n [tableHeight]=\"tableHeight\"\n [totalRecords]=\"totalRecords\"\n [loading]=\"loading\"\n [moreMenuActions]=\"moreMenuActions\"\n [canEdit]=\"canEdit\"\n [canDelete]=\"canDelete\"\n [highlightedRow]=\"restoredRow\"\n [primaryKey]=\"primaryKey\"\n (editClick)=\"openEditDialog($event)\"\n (deleteClick)=\"onDeleteClick($event)\"\n (pageChange)=\"onPageChange(pageChange, $event)\"\n (pageSizeChange)=\"onPageChange(pageSizeChange, $event)\"\n (sortChange)=\"sortChange.emit($event)\"\n [isSelectable]=\"isLister\"\n (onRowClick)=\"onRowClick.emit($event)\"\n />\n </div>\n }\n\n <!-- \u2500\u2500 Card View \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (viewMode === 'card') {\n\n <!-- Skeleton on initial load -->\n @if (loading && cardDatasource.length === 0) {\n <div class=\"osl-card-container my-2\" [style.height]=\"tableHeight\">\n <div class=\"osl-card-grid\">\n @for (sk of skeletonCardRows; track $index) {\n <div class=\"osl-card osl-card--skeleton\" [style.height.px]=\"cardHeightForVirtualscroll\">\n <div class=\"osl-card-inner\">\n <div class=\"osl-card-header\">\n <div class=\"osl-card-avatar osl-card-avatar--skeleton\"></div>\n <div style=\"flex:1;min-width:0\">\n <div class=\"osl-sk-line osl-sk-line--title\"></div>\n </div>\n </div>\n <div class=\"osl-card-divider\"></div>\n <div class=\"osl-card-body\">\n <div class=\"osl-card-field\"><div class=\"osl-sk-line osl-sk-line--label\"></div><div class=\"osl-sk-line\"></div></div>\n <div class=\"osl-card-field\"><div class=\"osl-sk-line osl-sk-line--label\"></div><div class=\"osl-sk-line osl-sk-line--short\"></div></div>\n <div class=\"osl-card-field\"><div class=\"osl-sk-line osl-sk-line--label\"></div><div class=\"osl-sk-line\"></div></div>\n </div>\n </div>\n </div>\n }\n </div>\n </div>\n }\n\n <!-- Empty state -->\n @else if (cardDatasource.length === 0 && !loading) {\n <div class=\"osl-card-container my-2\" [style.height]=\"tableHeight\">\n <div class=\"osl-card-empty\">\n <svg class=\"osl-card-empty-svg\" viewBox=\"0 0 180 160\" fill=\"none\">\n <ellipse cx=\"90\" cy=\"140\" rx=\"60\" ry=\"8\" fill=\"#f3f4f6\"/>\n <rect x=\"30\" y=\"30\" width=\"120\" height=\"96\" rx=\"10\" fill=\"#f9fafb\" stroke=\"#e5e7eb\" stroke-width=\"2\"/>\n <rect x=\"44\" y=\"48\" width=\"92\" height=\"10\" rx=\"5\" fill=\"#e5e7eb\"/>\n <rect x=\"44\" y=\"66\" width=\"72\" height=\"8\" rx=\"4\" fill=\"#f3f4f6\"/>\n <rect x=\"44\" y=\"82\" width=\"84\" height=\"8\" rx=\"4\" fill=\"#f3f4f6\"/>\n <rect x=\"44\" y=\"98\" width=\"56\" height=\"8\" rx=\"4\" fill=\"#f3f4f6\"/>\n <circle cx=\"140\" cy=\"108\" r=\"26\" fill=\"#fff\" stroke=\"#e5e7eb\" stroke-width=\"2\"/>\n <line x1=\"133\" y1=\"101\" x2=\"147\" y2=\"115\" stroke=\"#d1d5db\" stroke-width=\"2.5\" stroke-linecap=\"round\"/>\n <line x1=\"147\" y1=\"101\" x2=\"133\" y2=\"115\" stroke=\"#d1d5db\" stroke-width=\"2.5\" stroke-linecap=\"round\"/>\n </svg>\n <p class=\"osl-card-empty-title\">No records found</p>\n <p class=\"osl-card-empty-desc\">Try adjusting your search or filter criteria</p>\n </div>\n </div>\n }\n\n <!-- Virtual scroll card list -->\n @else {\n <div class=\"osl-card-vs-wrapper my-2\" [style.height]=\"tableHeight\">\n <cdk-virtual-scroll-viewport\n #cardViewport\n [itemSize]=\"cardHeightForVirtualscroll\"\n class=\"osl-card-virtual-viewport\"\n (scrolledIndexChange)=\"onVirtualScrollIndexChange($event)\">\n\n <div *cdkVirtualFor=\"let row of cardDatasource; trackBy: trackByCard; let i = index\"\n class=\"osl-card-vs-row\"\n [style.height.px]=\"cardHeightForVirtualscroll\">\n\n @if (cardTemplate) {\n <ng-container *ngTemplateOutlet=\"cardTemplate; context: { $implicit: row, index: i }\"></ng-container>\n } @else {\n <div class=\"osl-card\"\n [class.osl-card--highlighted]=\"isHighlightedCard(row)\"\n [class.osl-card--selectable]=\"isLister\"\n (click)=\"isLister ? onRowClick.emit(row) : null\">\n\n <div class=\"osl-card-inner\">\n\n <!-- Left: avatar + title -->\n <div class=\"osl-card-header\">\n <!-- @if (cardTitleColumn) {\n <div class=\"osl-card-avatar\">{{ getCardInitial(row) }}</div>\n } -->\n <div class=\"osl-card-title-wrap\">\n <span class=\"osl-card-title\">\n @if (cardTitleColumn) {\n @switch (cardTitleColumn.displayType) {\n @case ('date') { {{ row[cardTitleColumn.key] | date }} }\n @case ('datetime') { {{ row[cardTitleColumn.key] | date:'medium' }} }\n @case ('time') { {{ row[cardTitleColumn.key] | date:'shortTime' }} }\n @case ('customDateFormat') { {{ row[cardTitleColumn.key] | date:cardTitleColumn.customDateFormat }} }\n @default { {{ getCellValue(row, cardTitleColumn) }} }\n }\n }\n </span>\n </div>\n </div>\n\n <!-- Vertical divider -->\n <div class=\"osl-card-divider\"></div>\n\n <!-- Middle: body fields (horizontal) -->\n <div class=\"osl-card-body\">\n @for (col of cardBodyColumns; track col.key) {\n <div class=\"osl-card-field\">\n <span class=\"osl-card-label\">{{ col.label }}</span>\n <span class=\"osl-card-value\" [class.link]=\"col.displayType === 'link'\" (click)=\"col.click ? col.click(row, col) : null\">\n @switch (col.displayType) {\n @case ('date') { {{ row[col.key] | date }} }\n @case ('datetime') { {{ row[col.key] | date:'medium' }} }\n @case ('time') { {{ row[col.key] | date:'shortTime' }} }\n @case ('customDateFormat') { {{ row[col.key] | date:col.customDateFormat }} }\n @default { {{ getCellValue(row, col) }} }\n }\n </span>\n </div>\n }\n </div>\n\n <!-- Right: action buttons -->\n @if (!isLister && ((hasForm && (canEdit || canDelete)) || (moreMenuActions.length > 0 && hasVisibleActions(row)))) {\n <div class=\"osl-card-actions\">\n \n \n @if (hasForm && canDelete) {\n <button class=\"osl-card-action-btn osl-card-action-btn--delete\"\n (click)=\"$event.stopPropagation(); onDeleteClick(row)\" title=\"Delete\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polyline points=\"3 6 5 6 21 6\"/>\n <path d=\"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2\"/>\n </svg>\n </button>\n }\n </div>\n }\n @if (hasForm && canEdit) {\n <button class=\"osl-card-action-btn osl-card-action-btn--edit\"\n (click)=\"$event.stopPropagation(); openEditDialog(row)\" title=\"Edit\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7\"/>\n <path d=\"M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z\"/>\n </svg>\n </button>\n }\n\n @if (moreMenuActions.length > 0 && hasVisibleActions(row)) {\n <button class=\"osl-card-action-btn osl-card-action-btn--more\"\n (click)=\"toggleCardMenu(i, $event)\" title=\"More actions\">\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <circle cx=\"12\" cy=\"5\" r=\"2.2\"/>\n <circle cx=\"12\" cy=\"12\" r=\"2.2\"/>\n <circle cx=\"12\" cy=\"19\" r=\"2.2\"/>\n </svg>\n </button>\n }\n\n </div>\n </div>\n }\n </div>\n </cdk-virtual-scroll-viewport>\n </div>\n\n <!-- Infinite scroll loader (outside viewport) -->\n @if (loading) {\n <div class=\"osl-card-loader\">\n <div class=\"osl-card-loader-track\">\n <div class=\"osl-card-loader-bar\"></div>\n </div>\n <span class=\"osl-card-loader-text\">Loading more\u2026</span>\n </div>\n }\n\n <!-- All loaded footer (outside viewport) -->\n @if (allCardsLoaded && !loading && cardDatasource.length > 0) {\n <div class=\"osl-card-all-loaded\">\n <span class=\"osl-card-all-loaded-line\"></span>\n <span class=\"osl-card-all-loaded-text\">All {{ totalRecords || cardDatasource.length }} records loaded</span>\n <span class=\"osl-card-all-loaded-line\"></span>\n </div>\n }\n }\n }\n\n</div>\n\n<!-- Dialog: Form Body -->\n<ng-template #formBodyTpl>\n <osl-dynamic-form\n [skeletonLoading]=\"formLoading\"\n [elements]=\"formElements\"\n [(model)]=\"dialogModel\"\n ></osl-dynamic-form>\n</ng-template>\n\n<!-- Dialog: Form Footer -->\n<ng-template #formFooterTpl>\n <div class=\"osl-setup-dialog-footer\">\n <osl-button [loading]=\"saveLoading\" variant=\"secondary\" label=\"Save\" (click)=\"saveDialog()\"></osl-button>\n </div>\n</ng-template>\n\n<ng-template #customFooterWrapperTpl let-data>\n <ng-container *ngTemplateOutlet=\"customFormFooter!; context: { $implicit: { dialogModel: dialogModel, dialogMode: dialogMode, dialogRef: data.dialogRef } }\"></ng-container>\n</ng-template>\n\n<!-- Floating card more-actions menu \u2014 rendered outside card DOM to avoid transform containment -->\n@if (viewMode === 'card' && cardOpenMenuIndex !== null && moreMenuActions.length > 0) {\n <div class=\"osl-card-menu\"\n [style.top.px]=\"cardMenuPosition.top\"\n [style.left.px]=\"cardMenuPosition.left\"\n (click)=\"$event.stopPropagation()\">\n <div class=\"osl-card-menu-header\">Actions</div>\n @for (action of moreMenuActions; track $index) {\n @if (!action.hideIf || !action.hideIf(cardDatasource[cardOpenMenuIndex])) {\n <button class=\"osl-card-menu-item\"\n (click)=\"action.click(cardDatasource[cardOpenMenuIndex]); cardOpenMenuIndex = null\">\n <span class=\"osl-card-menu-dot\"></span>\n {{ action.labelIf ? action.labelIf(cardDatasource[cardOpenMenuIndex]) : action.label }}\n </button>\n }\n }\n </div>\n}\n", styles: [".osl-setup-header{display:flex;align-items:center;justify-content:space-between}.osl-setup-dialog-footer{display:flex;align-items:center;justify-content:flex-end;gap:10px;width:100%}.dialog-cancel-btn{display:flex;align-items:center;gap:6px;height:38px;font-size:13px;font-weight:500;border-color:var(--osl-border-color, #d1d5db);color:var(--osl-secondary, #6b7280);border-radius:var(--osl-border-radius, 4px);padding:0 16px;transition:all .2s ease}.dialog-cancel-btn mat-icon{font-size:17px;width:17px;height:17px}.dialog-cancel-btn:hover{border-color:#9ca3af;background:#f9fafb;color:#374151}.dialog-save-btn{display:flex;align-items:center;gap:6px;height:38px;font-size:13px;font-weight:500;background:linear-gradient(135deg,var(--osl-primary, #2563eb),#3b82f6);color:#fff;border-radius:var(--osl-border-radius, 4px);padding:0 18px;transition:all .2s ease;box-shadow:0 2px 8px #2563eb40}.dialog-save-btn mat-icon{font-size:17px;width:17px;height:17px}.dialog-save-btn:hover{background:linear-gradient(135deg,var(--osl-primary-hover, #1d4ed8),#2563eb);box-shadow:0 4px 14px #2563eb66;transform:translateY(-1px)}.dialog-save-btn:active{transform:translateY(0);box-shadow:0 2px 8px #2563eb40}.osl-view-toggle{display:flex;border:1.5px solid var(--osl-border-color, #e5e7eb);border-radius:8px;overflow:hidden;flex-shrink:0;background:#fff}.osl-view-toggle-btn{display:inline-flex;align-items:center;justify-content:center;width:34px;height:34px;border:none;background:transparent;cursor:pointer;color:#9ca3af;transition:background .15s,color .15s;padding:0}.osl-view-toggle-btn svg{width:16px;height:16px;transition:stroke .15s}.osl-view-toggle-btn:first-child{border-right:1.5px solid var(--osl-border-color, #e5e7eb)}.osl-view-toggle-btn--active{background:var(--osl-primary, #6366f1);color:#fff}.osl-view-toggle-btn--active svg{stroke:#fff}.osl-view-toggle-btn:not(.osl-view-toggle-btn--active):hover{background:#f3f4f6;color:#374151}.osl-card-container{overflow-y:auto;overflow-x:hidden;border-radius:12px;padding:4px 2px 16px;box-sizing:border-box}.osl-card-vs-wrapper{display:flex;flex-direction:column;overflow:hidden;box-sizing:border-box}.osl-card-virtual-viewport{flex:1;min-height:0;width:100%}.osl-card-vs-row{padding:0 2px 4px;box-sizing:border-box}.osl-card-vs-row>.osl-card{height:100%}.osl-card-grid{display:flex;flex-direction:column;gap:10px;padding:4px}@keyframes osl-card-enter{0%{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}.osl-card-grid .osl-card:nth-child(1){animation-delay:0s}.osl-card-grid .osl-card:nth-child(2){animation-delay:.03s}.osl-card-grid .osl-card:nth-child(3){animation-delay:.06s}.osl-card-grid .osl-card:nth-child(4){animation-delay:.09s}.osl-card-grid .osl-card:nth-child(5){animation-delay:.12s}.osl-card-grid .osl-card:nth-child(6){animation-delay:.15s}.osl-card-grid .osl-card:nth-child(7){animation-delay:.18s}.osl-card-grid .osl-card:nth-child(8){animation-delay:.21s}.osl-card{position:relative;background:#fff;border-radius:14px;border:1px solid #eaecf0;box-shadow:0 1px 4px #1018280f,0 1px 2px #1018280a;overflow:hidden;transition:transform .22s cubic-bezier(.34,1.56,.64,1),box-shadow .22s ease;animation:osl-card-enter .28s ease both}.osl-card:nth-child(6n+1){--card-accent: #6366f1;--card-accent-bg: #eef2ff}.osl-card:nth-child(6n+2){--card-accent: #0ea5e9;--card-accent-bg: #f0f9ff}.osl-card:nth-child(6n+3){--card-accent: #10b981;--card-accent-bg: #ecfdf5}.osl-card:nth-child(6n+4){--card-accent: #f59e0b;--card-accent-bg: #fffbeb}.osl-card:nth-child(6n+5){--card-accent: #f43f5e;--card-accent-bg: #fff1f2}.osl-card:nth-child(6n){--card-accent: #8b5cf6;--card-accent-bg: #faf5ff}.osl-card:before{content:\"\";position:absolute;top:0;left:0;bottom:0;width:4px;background:var(--card-accent, var(--osl-primary, #6366f1))}.osl-card:hover{transform:translateY(-2px);box-shadow:0 6px 20px #10182817,0 2px 8px #1018280d;border-color:#d0d5dd}.osl-card--highlighted{outline:2px solid var(--osl-primary, #6366f1);outline-offset:2px;animation:osl-card-enter .28s ease both,osl-card-highlight-pulse 2.5s ease-out .3s both}.osl-card--selectable{cursor:pointer}.osl-card--skeleton{pointer-events:none;animation:osl-card-enter .28s ease both}.osl-card--skeleton:hover{transform:none;box-shadow:0 1px 4px #1018280f}@keyframes osl-card-highlight-pulse{0%{box-shadow:0 0 0 4px #6366f14d}to{box-shadow:none}}.osl-card-inner{padding:12px 16px 12px 20px;display:flex;flex-direction:row;align-items:center;gap:0;min-height:0}.osl-card-header{display:flex;align-items:center;gap:12px;flex-shrink:0;width:220px;min-width:0;padding-right:16px}.osl-card-avatar{width:40px;height:40px;border-radius:10px;background:var(--card-accent, var(--osl-primary, #6366f1));color:#fff;font-size:16px;font-weight:700;display:flex;align-items:center;justify-content:center;flex-shrink:0;letter-spacing:0;box-shadow:0 4px 10px #00000026}.osl-card-avatar--skeleton{background:#e5e7eb;box-shadow:none;animation:osl-sk-pulse 1.4s ease-in-out infinite}.osl-card-title-wrap{flex:1;min-width:0}.osl-card-title{display:block;font-weight:700;font-size:14px;color:#101828;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;line-height:1.4}.osl-card-actions{display:flex;align-items:center;gap:6px;flex-shrink:0;margin-left:12px}.osl-card-action-btn{display:inline-flex;align-items:center;justify-content:center;width:30px;height:30px;border-radius:8px;border:1.5px solid #e4e7ec;background:#fff;cursor:pointer;transition:background .15s,border-color .15s,transform .15s,box-shadow .15s;padding:0}.osl-card-action-btn svg{width:14px;height:14px;transition:stroke .15s,fill .15s}.osl-card-action-btn--more svg{fill:#9ca3af;stroke:none}.osl-card-action-btn--more:hover{background:var(--card-accent-bg, #eef2ff);border-color:var(--card-accent, var(--osl-primary, #6366f1));box-shadow:0 2px 6px #00000014;transform:scale(1.05)}.osl-card-action-btn--more:hover svg{fill:var(--card-accent, var(--osl-primary, #6366f1))}.osl-card-action-btn--edit svg{stroke:#6b7280}.osl-card-action-btn--edit:hover{background:var(--card-accent-bg, #eef2ff);border-color:var(--card-accent, var(--osl-primary, #6366f1));box-shadow:0 2px 6px #00000014;transform:scale(1.05)}.osl-card-action-btn--edit:hover svg{stroke:var(--card-accent, var(--osl-primary, #6366f1))}.osl-card-action-btn--delete svg{stroke:#9ca3af}.osl-card-action-btn--delete:hover{background:#fff1f2;border-color:#f43f5e;box-shadow:0 2px 6px #f43f5e26;transform:scale(1.05)}.osl-card-action-btn--delete:hover svg{stroke:#f43f5e}.osl-card-divider{width:1px;align-self:stretch;background:#f2f4f7;margin:0 16px;flex-shrink:0}.osl-card-body{display:flex;flex-direction:row;align-items:center;gap:24px;flex:1;min-width:0;flex-wrap:wrap}.osl-card-field{display:flex;flex-direction:column;gap:3px;flex:1;min-width:100px;max-width:200px}.osl-card-label{font-size:10px;font-weight:600;color:#9ca3af;text-transform:uppercase;letter-spacing:.06em;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.osl-card-value{font-size:13px;font-weight:500;color:#374151;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}.osl-card-value.link{text-decoration:underline;color:#2563eb;cursor:pointer}.osl-card-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:320px;gap:12px;padding:40px 20px}.osl-card-empty-svg{width:180px;height:auto;opacity:.85}.osl-card-empty-title{font-size:16px;font-weight:600;color:#374151;margin:4px 0 0;text-align:center}.osl-card-empty-desc{font-size:13px;color:#9ca3af;margin:0;text-align:center}.osl-card-loader{display:flex;flex-direction:column;align-items:center;gap:10px;padding:28px 20px 16px}.osl-card-loader-track{width:160px;height:3px;background:#f3f4f6;border-radius:99px;overflow:hidden}.osl-card-loader-bar{height:100%;width:40%;background:linear-gradient(90deg,transparent,var(--osl-primary, #6366f1),transparent);border-radius:99px;animation:osl-loader-sweep 1.2s ease-in-out infinite}@keyframes osl-loader-sweep{0%{transform:translate(-200%)}to{transform:translate(500%)}}.osl-card-loader-text{font-size:12px;color:#9ca3af;font-weight:500}.osl-card-all-loaded{display:flex;align-items:center;gap:12px;padding:20px 16px 8px;justify-content:center}.osl-card-all-loaded-line{flex:1;max-width:80px;height:1px;background:#e5e7eb}.osl-card-all-loaded-text{font-size:11px;color:#d1d5db;font-weight:600;text-transform:uppercase;letter-spacing:.06em;white-space:nowrap}@keyframes osl-sk-pulse{0%,to{opacity:1}50%{opacity:.4}}.osl-sk-line{height:12px;background:#e9eaec;border-radius:6px;animation:osl-sk-pulse 1.4s ease-in-out infinite;width:80%}.osl-sk-line--title{height:14px;width:65%}.osl-sk-line--label{width:40%;height:10px}.osl-sk-line--short{width:50%}.osl-card-menu{position:fixed;z-index:9999;min-width:196px;background:#fff;border:1px solid #eaecf0;border-radius:12px;box-shadow:0 12px 32px #10182824,0 4px 12px #1018280f;overflow:hidden;animation:osl-menu-pop .16s cubic-bezier(.16,1,.3,1)}@keyframes osl-menu-pop{0%{opacity:0;transform:translateY(-8px) scale(.96)}to{opacity:1;transform:translateY(0) scale(1)}}.osl-card-menu-header{padding:10px 14px 7px;font-size:10px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:#9ca3af;border-bottom:1px solid #f2f4f7;-webkit-user-select:none;user-select:none}.osl-card-menu-item{display:flex;align-items:center;gap:10px;width:100%;padding:10px 14px;background:transparent;border:none;border-left:3px solid transparent;border-bottom:1px solid #f9fafb;text-align:left;font-size:13px;font-weight:500;font-family:inherit;color:#374151;cursor:pointer;white-space:nowrap;transition:background .12s,color .12s,border-left-color .12s}.osl-card-menu-item:last-child{border-bottom:none}.osl-card-menu-item:hover{background:var(--card-accent-bg, #f5f3ff);color:var(--card-accent, var(--osl-primary, #6366f1));border-left-color:var(--card-accent, var(--osl-primary, #6366f1))}.osl-card-menu-item:hover .osl-card-menu-dot{background:var(--card-accent, var(--osl-primary, #6366f1));box-shadow:0 0 0 3px #6366f129}.osl-card-menu-dot{flex-shrink:0;width:6px;height:6px;border-radius:50%;background:#d1d5db;transition:background .12s,box-shadow .12s}\n"] }]
|
|
3855
3881
|
}], propDecorators: { formBodyTpl: [{
|
|
3856
3882
|
type: ViewChild,
|
|
3857
3883
|
args: ['formBodyTpl']
|
|
@@ -3867,9 +3893,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
3867
3893
|
}], gridRef: [{
|
|
3868
3894
|
type: ViewChild,
|
|
3869
3895
|
args: ['gridRef']
|
|
3870
|
-
}],
|
|
3896
|
+
}], cardViewport: [{
|
|
3871
3897
|
type: ViewChild,
|
|
3872
|
-
args: ['
|
|
3898
|
+
args: ['cardViewport']
|
|
3873
3899
|
}], title: [{
|
|
3874
3900
|
type: Input,
|
|
3875
3901
|
args: ['title']
|
|
@@ -3948,6 +3974,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
3948
3974
|
}], cardTemplate: [{
|
|
3949
3975
|
type: Input,
|
|
3950
3976
|
args: ['cardTemplate']
|
|
3977
|
+
}], cardHeightForVirtualscroll: [{
|
|
3978
|
+
type: Input,
|
|
3979
|
+
args: ['cardHeightForVirtualscroll']
|
|
3951
3980
|
}], onSearch: [{
|
|
3952
3981
|
type: Output
|
|
3953
3982
|
}], onAdd: [{
|
|
@@ -4200,7 +4229,7 @@ class OslAutocompleteLister {
|
|
|
4200
4229
|
this.dialogRef.close(event);
|
|
4201
4230
|
}
|
|
4202
4231
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: OslAutocompleteLister, deps: [{ token: i1.MatDialogRef }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
|
|
4203
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.9", type: OslAutocompleteLister, isStandalone: false, selector: "osl-autocomplete-lister", inputs: { data: "data" }, viewQueries: [{ propertyName: "setup", first: true, predicate: ["setup"], descendants: true }], ngImport: i0, template: "<div class=\"p-4\">\r\n <osl-setup #setup [loading]=\"loader\" [autoMode]=\"false\" [title]=\"autocompleteData?.title\" (sortChange)=\"onSortChange($event)\" [totalRecords]=\"recordCount\" (pageSizeChange)=\"onPageChange($event)\" (pageChange)=\"onPageChange($event)\" (onRowClick)=\"onRowClick($event)\" [columns]=\"column\" [datasource]=\"datasource\" [isLister]=\"true\"></osl-setup>\r\n \r\n</div>", styles: [""], dependencies: [{ kind: "component", type: OslSetup, selector: "osl-setup", inputs: ["title", "columns", "datasource", "isPaginated", "pageSize", "autoMode", "tableHeight", "totalRecords", "loading", "dialogWidth", "formElements", "beforeDisplay", "onAddEditFn", "isLister", "canAdd", "canEdit", "canDelete", "moreMenuActions", "customFormFooter", "customHeaderTemp", "partialCustomHeaderTemp", "stateKey", "primaryKey", "onSave", "cardPageSize", "cardTemplate"], outputs: ["onSearch", "onAdd", "onEdit", "onDelete", "pageChange", "pageSizeChange", "sortChange", "onRowClick", "onStateRestored"] }] });
|
|
4232
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.9", type: OslAutocompleteLister, isStandalone: false, selector: "osl-autocomplete-lister", inputs: { data: "data" }, viewQueries: [{ propertyName: "setup", first: true, predicate: ["setup"], descendants: true }], ngImport: i0, template: "<div class=\"p-4\">\r\n <osl-setup #setup [loading]=\"loader\" [autoMode]=\"false\" [title]=\"autocompleteData?.title\" (sortChange)=\"onSortChange($event)\" [totalRecords]=\"recordCount\" (pageSizeChange)=\"onPageChange($event)\" (pageChange)=\"onPageChange($event)\" (onRowClick)=\"onRowClick($event)\" [columns]=\"column\" [datasource]=\"datasource\" [isLister]=\"true\"></osl-setup>\r\n \r\n</div>", styles: [""], dependencies: [{ kind: "component", type: OslSetup, selector: "osl-setup", inputs: ["title", "columns", "datasource", "isPaginated", "pageSize", "autoMode", "tableHeight", "totalRecords", "loading", "dialogWidth", "formElements", "beforeDisplay", "onAddEditFn", "isLister", "canAdd", "canEdit", "canDelete", "moreMenuActions", "customFormFooter", "customHeaderTemp", "partialCustomHeaderTemp", "stateKey", "primaryKey", "onSave", "cardPageSize", "cardTemplate", "cardHeightForVirtualscroll"], outputs: ["onSearch", "onAdd", "onEdit", "onDelete", "pageChange", "pageSizeChange", "sortChange", "onRowClick", "onStateRestored"] }] });
|
|
4204
4233
|
}
|
|
4205
4234
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: OslAutocompleteLister, decorators: [{
|
|
4206
4235
|
type: Component,
|