osl-base-extended 9.6.0 → 9.8.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.
@@ -1164,7 +1164,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
1164
1164
  class Oslinput {
1165
1165
  label = '';
1166
1166
  required = false;
1167
- disabled = false;
1167
+ _isDisabled;
1168
+ set disabled(val) {
1169
+ this._isDisabled = val;
1170
+ }
1171
+ get disabled() {
1172
+ return this._isDisabled;
1173
+ }
1168
1174
  model = '';
1169
1175
  type = 'text';
1170
1176
  placeholder = '';
@@ -3302,12 +3308,18 @@ class OslSearchbar {
3302
3308
  label = "Type to Search...";
3303
3309
  onSearch = new EventEmitter();
3304
3310
  searchQuery = "";
3311
+ focused = false;
3305
3312
  searchControl = new FormControl('');
3306
3313
  onKeyChange() {
3307
3314
  if (!this.searchQuery) {
3308
3315
  this.onSearch.emit(this.searchQuery);
3309
3316
  }
3310
3317
  }
3318
+ clearSearch() {
3319
+ this.searchQuery = '';
3320
+ this.searchControl.setValue('', { emitEvent: false });
3321
+ this.onSearch.emit('');
3322
+ }
3311
3323
  ngOnInit() {
3312
3324
  this.searchControl.valueChanges.pipe(debounceTime$1(300), distinctUntilChanged$1()).subscribe(value => {
3313
3325
  if (value) {
@@ -3316,11 +3328,11 @@ class OslSearchbar {
3316
3328
  });
3317
3329
  }
3318
3330
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: OslSearchbar, deps: [], target: i0.ɵɵFactoryTarget.Component });
3319
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.9", type: OslSearchbar, isStandalone: false, selector: "osl-searchbar", inputs: { label: "label" }, outputs: { onSearch: "onSearch" }, ngImport: i0, template: " <div class=\"search-wrap\" [class.focused]=\"searchQuery.length > 0\">\r\n <svg class=\"search-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\">\r\n <circle cx=\"11\" cy=\"11\" r=\"8\"/>\r\n <line x1=\"21\" y1=\"21\" x2=\"16.65\" y2=\"16.65\"/>\r\n </svg>\r\n <input\r\n type=\"text\"\r\n (keyup)=\"onKeyChange()\"\r\n [formControl]=\"searchControl\"\r\n class=\"search-input\"\r\n [placeholder]=\"label\"\r\n [(ngModel)]=\"searchQuery\"\r\n />\r\n \r\n \r\n </div>", styles: [".search-wrap{display:flex;align-items:center;gap:10px;width:100%;border:1px solid var(--osl-border);border-radius:5px;padding:0 14px;height:30px;transition:border-color var(--transition-fast),box-shadow var(--transition-fast)}.search-icon{width:15px;height:15px;color:var(--text-muted);flex-shrink:0;transition:color var(--transition-fast)}.search-wrap:focus-within .search-icon{color:var(--accent-cyan)}.search-input{flex:1;background:none;border:none;outline:none;color:var(--text-primary);font-family:Lexend Deca,sans-serif;font-size:13px}.search-input::placeholder{color:var(--text-muted)}.search-kbd{font-size:10px;color:var(--text-muted);background:var(--bg-hover);border:1px solid var(--osl-border-subtle);border-radius:4px;padding:2px 5px;font-family:Lexend Deca,sans-serif;flex-shrink:0}.search-clear{width:16px;height:16px;color:var(--text-muted);display:flex;align-items:center;justify-content:center;flex-shrink:0;border-radius:50%;transition:color var(--transition-fast)}.search-clear svg{width:12px;height:12px}.search-clear:hover{color:var(--text-primary)}\n"], dependencies: [{ kind: "directive", type: i1$2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }] });
3331
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: OslSearchbar, isStandalone: false, selector: "osl-searchbar", inputs: { label: "label" }, outputs: { onSearch: "onSearch" }, ngImport: i0, template: "<div class=\"osl-searchbar-wrap\" [class.osl-searchbar-wrap--focused]=\"focused\">\n <svg class=\"osl-searchbar-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <circle cx=\"11\" cy=\"11\" r=\"8\"/>\n <line x1=\"21\" y1=\"21\" x2=\"16.65\" y2=\"16.65\"/>\n </svg>\n <input\n type=\"text\"\n class=\"osl-searchbar-input\"\n (keyup)=\"onKeyChange()\"\n [formControl]=\"searchControl\"\n [placeholder]=\"label\"\n [(ngModel)]=\"searchQuery\"\n (focus)=\"focused = true\"\n (blur)=\"focused = false\"\n />\n @if (searchQuery) {\n <button type=\"button\" class=\"osl-searchbar-clear\" tabindex=\"-1\" aria-label=\"Clear search\" (click)=\"clearSearch()\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"/>\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"/>\n </svg>\n </button>\n }\n</div>\n", styles: [":host{display:inline-flex;width:260px;max-width:100%;flex-shrink:0}.osl-searchbar-wrap{position:relative;display:flex;align-items:center;gap:8px;width:100%;height:36px;padding:0 14px;background:#f1f2f4;border:1px solid transparent;border-radius:999px;transition:background-color .2s ease,border-color .2s ease,box-shadow .2s ease}.osl-searchbar-wrap:hover{background:#e7e9ec}.osl-searchbar-wrap--focused,.osl-searchbar-wrap--focused:hover{background:#fff;border-color:var(--osl-primary, #6366f1);box-shadow:0 0 0 3px color-mix(in srgb,var(--osl-primary, #6366f1) 14%,transparent),0 1px 2px #1018280a}.osl-searchbar-icon{width:16px;height:16px;color:#9ca3af;flex-shrink:0;transition:color .2s ease}.osl-searchbar-wrap--focused .osl-searchbar-icon{color:var(--osl-primary, #6366f1)}.osl-searchbar-input{flex:1;min-width:0;height:100%;border:none;outline:none;background:transparent;color:#1f2937;font-family:inherit;font-size:var(--osl-text-font-size, 13px)}.osl-searchbar-input::placeholder{color:#9ca3af}.osl-searchbar-clear{display:flex;align-items:center;justify-content:center;flex-shrink:0;width:20px;height:20px;padding:0;border:none;border-radius:50%;background:transparent;color:#9ca3af;cursor:pointer;transition:background-color .15s ease,color .15s ease,transform .1s ease;animation:osl-searchbar-clear-in .15s ease}.osl-searchbar-clear svg{width:12px;height:12px}.osl-searchbar-clear:hover{background:#fff1f2;color:#f43f5e}.osl-searchbar-clear:active{transform:scale(.88)}@keyframes osl-searchbar-clear-in{0%{opacity:0;transform:scale(.5)}to{opacity:1;transform:scale(1)}}\n"], dependencies: [{ kind: "directive", type: i1$2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }] });
3320
3332
  }
3321
3333
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: OslSearchbar, decorators: [{
3322
3334
  type: Component,
3323
- args: [{ selector: 'osl-searchbar', standalone: false, template: " <div class=\"search-wrap\" [class.focused]=\"searchQuery.length > 0\">\r\n <svg class=\"search-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\">\r\n <circle cx=\"11\" cy=\"11\" r=\"8\"/>\r\n <line x1=\"21\" y1=\"21\" x2=\"16.65\" y2=\"16.65\"/>\r\n </svg>\r\n <input\r\n type=\"text\"\r\n (keyup)=\"onKeyChange()\"\r\n [formControl]=\"searchControl\"\r\n class=\"search-input\"\r\n [placeholder]=\"label\"\r\n [(ngModel)]=\"searchQuery\"\r\n />\r\n \r\n \r\n </div>", styles: [".search-wrap{display:flex;align-items:center;gap:10px;width:100%;border:1px solid var(--osl-border);border-radius:5px;padding:0 14px;height:30px;transition:border-color var(--transition-fast),box-shadow var(--transition-fast)}.search-icon{width:15px;height:15px;color:var(--text-muted);flex-shrink:0;transition:color var(--transition-fast)}.search-wrap:focus-within .search-icon{color:var(--accent-cyan)}.search-input{flex:1;background:none;border:none;outline:none;color:var(--text-primary);font-family:Lexend Deca,sans-serif;font-size:13px}.search-input::placeholder{color:var(--text-muted)}.search-kbd{font-size:10px;color:var(--text-muted);background:var(--bg-hover);border:1px solid var(--osl-border-subtle);border-radius:4px;padding:2px 5px;font-family:Lexend Deca,sans-serif;flex-shrink:0}.search-clear{width:16px;height:16px;color:var(--text-muted);display:flex;align-items:center;justify-content:center;flex-shrink:0;border-radius:50%;transition:color var(--transition-fast)}.search-clear svg{width:12px;height:12px}.search-clear:hover{color:var(--text-primary)}\n"] }]
3335
+ args: [{ selector: 'osl-searchbar', standalone: false, template: "<div class=\"osl-searchbar-wrap\" [class.osl-searchbar-wrap--focused]=\"focused\">\n <svg class=\"osl-searchbar-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <circle cx=\"11\" cy=\"11\" r=\"8\"/>\n <line x1=\"21\" y1=\"21\" x2=\"16.65\" y2=\"16.65\"/>\n </svg>\n <input\n type=\"text\"\n class=\"osl-searchbar-input\"\n (keyup)=\"onKeyChange()\"\n [formControl]=\"searchControl\"\n [placeholder]=\"label\"\n [(ngModel)]=\"searchQuery\"\n (focus)=\"focused = true\"\n (blur)=\"focused = false\"\n />\n @if (searchQuery) {\n <button type=\"button\" class=\"osl-searchbar-clear\" tabindex=\"-1\" aria-label=\"Clear search\" (click)=\"clearSearch()\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"/>\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"/>\n </svg>\n </button>\n }\n</div>\n", styles: [":host{display:inline-flex;width:260px;max-width:100%;flex-shrink:0}.osl-searchbar-wrap{position:relative;display:flex;align-items:center;gap:8px;width:100%;height:36px;padding:0 14px;background:#f1f2f4;border:1px solid transparent;border-radius:999px;transition:background-color .2s ease,border-color .2s ease,box-shadow .2s ease}.osl-searchbar-wrap:hover{background:#e7e9ec}.osl-searchbar-wrap--focused,.osl-searchbar-wrap--focused:hover{background:#fff;border-color:var(--osl-primary, #6366f1);box-shadow:0 0 0 3px color-mix(in srgb,var(--osl-primary, #6366f1) 14%,transparent),0 1px 2px #1018280a}.osl-searchbar-icon{width:16px;height:16px;color:#9ca3af;flex-shrink:0;transition:color .2s ease}.osl-searchbar-wrap--focused .osl-searchbar-icon{color:var(--osl-primary, #6366f1)}.osl-searchbar-input{flex:1;min-width:0;height:100%;border:none;outline:none;background:transparent;color:#1f2937;font-family:inherit;font-size:var(--osl-text-font-size, 13px)}.osl-searchbar-input::placeholder{color:#9ca3af}.osl-searchbar-clear{display:flex;align-items:center;justify-content:center;flex-shrink:0;width:20px;height:20px;padding:0;border:none;border-radius:50%;background:transparent;color:#9ca3af;cursor:pointer;transition:background-color .15s ease,color .15s ease,transform .1s ease;animation:osl-searchbar-clear-in .15s ease}.osl-searchbar-clear svg{width:12px;height:12px}.osl-searchbar-clear:hover{background:#fff1f2;color:#f43f5e}.osl-searchbar-clear:active{transform:scale(.88)}@keyframes osl-searchbar-clear-in{0%{opacity:0;transform:scale(.5)}to{opacity:1;transform:scale(1)}}\n"] }]
3324
3336
  }], propDecorators: { label: [{
3325
3337
  type: Input,
3326
3338
  args: ['label']
@@ -3606,6 +3618,7 @@ class OslSetup {
3606
3618
  canEdit = true;
3607
3619
  canDelete = true;
3608
3620
  moreMenuActions = [];
3621
+ moreMenuHeaderActions = [];
3609
3622
  customFormFooter;
3610
3623
  customHeaderTemp;
3611
3624
  partialCustomHeaderTemp;
@@ -3639,8 +3652,12 @@ class OslSetup {
3639
3652
  cardPageSizeOptions = [10, 25, 50, 100];
3640
3653
  cardOpenMenuIndex = null;
3641
3654
  cardMenuPosition = { top: 0, left: 0 };
3655
+ // ── Header more-actions menu state ─────────────────────────────
3656
+ headerMenuOpen = false;
3657
+ headerMenuPosition = { top: 0, left: 0 };
3642
3658
  onDocumentClick() {
3643
3659
  this.cardOpenMenuIndex = null;
3660
+ this.headerMenuOpen = false;
3644
3661
  }
3645
3662
  get hasForm() {
3646
3663
  return this.formElements?.length > 0 || !!this.onAddEditFn;
@@ -3807,6 +3824,21 @@ class OslSetup {
3807
3824
  this.cardMenuPosition = { top: rect.bottom + 6, left };
3808
3825
  this.cardOpenMenuIndex = index;
3809
3826
  }
3827
+ get hasVisibleHeaderActions() {
3828
+ return this.moreMenuHeaderActions.some(a => !a.hideIf || !a.hideIf(undefined));
3829
+ }
3830
+ toggleHeaderMenu(event) {
3831
+ event.stopPropagation();
3832
+ if (this.headerMenuOpen) {
3833
+ this.headerMenuOpen = false;
3834
+ return;
3835
+ }
3836
+ const rect = event.currentTarget.getBoundingClientRect();
3837
+ const menuWidth = 196;
3838
+ const left = Math.min(Math.max(rect.right - menuWidth, 8), window.innerWidth - menuWidth - 8);
3839
+ this.headerMenuPosition = { top: rect.bottom + 6, left };
3840
+ this.headerMenuOpen = true;
3841
+ }
3810
3842
  isHighlightedCard(row) {
3811
3843
  if (!this.restoredRow || !this.primaryKey)
3812
3844
  return false;
@@ -4028,11 +4060,11 @@ class OslSetup {
4028
4060
  });
4029
4061
  }
4030
4062
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: OslSetup, deps: [], target: i0.ɵɵFactoryTarget.Component });
4031
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: OslSetup, isStandalone: false, selector: "osl-setup", inputs: { title: "title", titleIcon: "titleIcon", columns: "columns", datasource: "datasource", isPaginated: "isPaginated", pageSize: "pageSize", autoMode: "autoMode", tableHeight: "tableHeight", totalRecords: "totalRecords", loading: "loading", dialogWidth: "dialogWidth", isDraggable: "isDraggable", 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", cardCol: "cardCol" }, 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: "cardGridRef", first: true, predicate: ["cardGridRef"], 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 <div class=\"d-flex align-items-center\">\n @if(titleIcon){\n\n <span class=\"title-icon mx-2 mb-1\" [innerHTML]=\"renderIcon(titleIcon)\"></span>\n }\n <h5 class=\"mb-0\">{{ title }}</h5>\n\n </div>\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 && cardPagedData.length === 0) {\n <div class=\"osl-card-grid my-2\">\n @for (sk of skeletonCardRows; track $index) {\n <div class=\"osl-card osl-card--skeleton\">\n <div class=\"osl-card-header osl-card-header--skeleton\">\n <div class=\"osl-card-title-wrap\">\n <div class=\"osl-sk-line osl-sk-line--title\"></div>\n </div>\n </div>\n <div class=\"osl-card-body\">\n <div class=\"row g-0\">\n <div class=\"col-6 osl-card-field\"><div class=\"osl-sk-line osl-sk-line--label\"></div><div class=\"osl-sk-line\"></div></div>\n <div class=\"col-6 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=\"col-6 osl-card-field\"><div class=\"osl-sk-line osl-sk-line--label\"></div><div class=\"osl-sk-line\"></div></div>\n <div class=\"col-6 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>\n </div>\n </div>\n }\n </div>\n }\n\n <!-- Empty state -->\n @else if (cardPagedData.length === 0 && !loading) {\n <div class=\"osl-card-empty my-2\">\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 #cardGridRef class=\"osl-card-grid my-2\" [style.height]=\"tableHeight\">\n @for (row of cardPagedData; track row[primaryKey] ?? $index; let i = $index) {\n\n @if (cardTemplate) {\n <ng-container *ngTemplateOutlet=\"cardTemplate; context: { $implicit: row, index: i }\"></ng-container>\n } @else {\n <div class=\"osl-card my-2\"\n [class.osl-card--selectable]=\"isLister\"\n [class.osl-card--highlighted]=\"isHighlightedCard(row)\"\n (click)=\"isLister ? onRowClick.emit(row) : null\">\n\n <!-- Card Header -->\n <div class=\"osl-card-header\">\n <!-- <svg class=\"osl-card-watermark\" viewBox=\"0 0 120 64\" fill=\"currentColor\" aria-hidden=\"true\">\n <path d=\"M10 42 L18 52 L102 52 L110 42 Z\"/>\n <rect x=\"20\" y=\"34\" width=\"80\" height=\"9\" rx=\"1\"/>\n <rect x=\"26\" y=\"20\" width=\"16\" height=\"15\" rx=\"1\"/>\n <rect x=\"46\" y=\"20\" width=\"16\" height=\"15\" rx=\"1\"/>\n <rect x=\"66\" y=\"20\" width=\"16\" height=\"15\" rx=\"1\"/>\n <rect x=\"85\" y=\"14\" width=\"15\" height=\"21\" rx=\"2\"/>\n <rect x=\"90.5\" y=\"7\" width=\"2\" height=\"9\" rx=\"1\"/>\n <path d=\"M4 57 Q18 53 32 57 Q46 61 60 57 Q74 53 88 57 Q102 61 116 57\" stroke=\"currentColor\" stroke-width=\"2\" fill=\"none\" stroke-linecap=\"round\"/>\n </svg> -->\n <div class=\"osl-card-title-wrap\">\n <span class=\"osl-card-title\"\n >\n @if (cardTitleColumn) {\n <span\n >\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 </span>\n }\n </span>\n </div>\n @if (!isLister && ((hasForm && (canEdit || canDelete)) || (moreMenuActions.length > 0 && hasVisibleActions(row)))) {\n <div class=\"osl-card-actions\">\n @if (hasForm && canEdit) {\n <button class=\"osl-card-action-btn osl-card-action-btn--edit\"\n (click)=\"$event.stopPropagation(); openEditDialog(row)\"\n [oslTooltip]=\"'Edit'\" oslTooltipPosition=\"bottom\">\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 @if (hasForm && canDelete) {\n <button class=\"osl-card-action-btn osl-card-action-btn--delete\"\n (click)=\"$event.stopPropagation(); onDeleteClick(row)\"\n [oslTooltip]=\"'Delete'\" oslTooltipPosition=\"bottom\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" xmlns=\"http://www.w3.org/2000/svg\">\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 <line x1=\"10\" y1=\"11\" x2=\"10\" y2=\"17\"/><line x1=\"14\" y1=\"11\" x2=\"14\" y2=\"17\"/>\n </svg>\n </button>\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)\"\n [oslTooltip]=\"'More actions'\" oslTooltipPosition=\"bottom\">\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 </div>\n }\n </div>\n\n <!-- Card Body -->\n @if (cardBodyColumns.length > 0) {\n <div class=\"osl-card-body\">\n <div class=\"row g-0\">\n @for (col of cardBodyColumns; track col.key) {\n <div [class]=\"'col-' + cardCol + ' 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\n <span>\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 </span>\n </div>\n }\n </div>\n </div>\n }\n\n </div>\n }\n }\n </div>\n }\n\n <!-- Card Paginator -->\n @if (isPaginated && _cardTotal > 0) {\n <div class=\"osl-grid-pagination\">\n <span class=\"osl-grid-pagination__info\">\n @if (loading) {\n Loading...\n } @else {\n {{ cardStartRecord }}\u2013{{ cardEndRecord }} of {{ _cardTotal }} records\n }\n </span>\n\n <div class=\"osl-grid-pagination__controls\">\n <button class=\"osl-grid-page-btn osl-grid-page-btn--nav\"\n (click)=\"cardGoToPage(1)\"\n [disabled]=\"cardCurrentPage === 1 || loading\" title=\"First page\">\u00AB</button>\n <button class=\"osl-grid-page-btn osl-grid-page-btn--nav\"\n (click)=\"cardGoToPage(cardCurrentPage - 1)\"\n [disabled]=\"cardCurrentPage === 1 || loading\" title=\"Previous page\">\u2039 Prev</button>\n\n @for (page of cardPageNumbers; track $index) {\n @if (page === -1) {\n <span class=\"osl-grid-page-ellipsis\">\u2026</span>\n } @else {\n <button class=\"osl-grid-page-btn osl-grid-page-btn--num\"\n [class.osl-grid-page-btn--active]=\"page === cardCurrentPage\"\n [disabled]=\"loading\"\n (click)=\"cardGoToPage(page)\">{{ page }}</button>\n }\n }\n\n <button class=\"osl-grid-page-btn osl-grid-page-btn--nav\"\n (click)=\"cardGoToPage(cardCurrentPage + 1)\"\n [disabled]=\"cardCurrentPage === cardTotalPages || loading\" title=\"Next page\">Next \u203A</button>\n <button class=\"osl-grid-page-btn osl-grid-page-btn--nav\"\n (click)=\"cardGoToPage(cardTotalPages)\"\n [disabled]=\"cardCurrentPage === cardTotalPages || loading\" title=\"Last page\">\u00BB</button>\n </div>\n\n <div class=\"osl-grid-pagination__size\">\n <select class=\"osl-grid-page-size\"\n [ngModel]=\"_effectiveCardPageSize\"\n (ngModelChange)=\"cardOnPageSizeChange($event)\"\n [disabled]=\"loading\">\n @for (opt of cardPageSizeOptions; track opt) {\n <option [value]=\"opt\">{{ opt }} per page</option>\n }\n </select>\n </div>\n </div>\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 -->\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(cardPagedData[cardOpenMenuIndex])) {\n <button class=\"osl-card-menu-item\"\n (click)=\"action.click(cardPagedData[cardOpenMenuIndex]); cardOpenMenuIndex = null\">\n <span class=\"osl-card-menu-dot\"></span>\n {{ action.labelIf ? action.labelIf(cardPagedData[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: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: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-grid{display:flexbox;width:100%;overflow:auto}@keyframes osl-card-enter{0%{opacity:0;transform:translateY(8px)}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:12px;border:1px solid #eaecf0;box-shadow:0 1px 3px #1018280f,0 1px 2px #1018280a;overflow:hidden;transition:transform .2s cubic-bezier(.34,1.56,.64,1),box-shadow .2s ease,border-color .2s ease;animation:osl-card-enter .26s ease both}.osl-card:hover{transform:translateY(-2px);box-shadow:0 8px 24px #1018281a,0 2px 6px #1018280f}.osl-card--selectable{cursor:pointer}.osl-card--highlighted{border-color:var(--card-accent, #6366f1);box-shadow:0 0 0 3px color-mix(in srgb,var(--card-accent, #6366f1) 20%,transparent),0 4px 16px #1018281a}.osl-card--skeleton{pointer-events:none;animation:osl-card-enter .26s ease both}.osl-card--skeleton:hover{transform:none;box-shadow:0 1px 3px #1018280f}@keyframes osl-card-highlight-pulse{0%{box-shadow:0 0 0 4px color-mix(in srgb,var(--card-accent, #6366f1) 30%,transparent)}to{box-shadow:none}}.osl-card-header{position:relative;overflow:hidden;display:flex;align-items:center;gap:10px;padding:9px 12px 9px 14px;border-bottom:1px solid rgba(0,0,0,.05);min-height:42px}.osl-card-header--skeleton{background:#f9fafb}.osl-card-watermark{position:absolute;right:8px;top:50%;transform:translateY(-50%);width:72px;height:40px;color:var(--card-accent, var(--osl-primary, #6366f1));opacity:.1;pointer-events:none;flex-shrink:0}.osl-card-title-wrap{flex:1;min-width:0;position:relative;z-index:1}.osl-card-title{display:block;font-weight:700;font-size:13px;color:#1e293b;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;line-height:1.4}.osl-card-actions{display:flex;align-items:center;gap:4px;flex-shrink:0;position:relative;z-index:1}.osl-card-action-btn{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;border-radius:7px;border:1.5px solid rgba(255,255,255,.9);background:#fffc;cursor:pointer;transition:background .15s,border-color .15s,transform .15s,box-shadow .15s;padding:0}.osl-card-action-btn svg{width:13px;height:13px;transition:stroke .15s,fill .15s}.osl-card-action-btn--edit svg{stroke:#6b7280}.osl-card-action-btn--edit:hover{background:#fff;border-color:var(--card-accent, #6366f1);box-shadow:0 2px 8px #0000001f;transform:scale(1.1)}.osl-card-action-btn--edit:hover svg{stroke:var(--card-accent, #6366f1)}.osl-card-action-btn--delete svg{stroke:#9ca3af}.osl-card-action-btn--delete:hover{background:#fff1f2;border-color:#f43f5e;box-shadow:0 2px 8px #f43f5e33;transform:scale(1.1)}.osl-card-action-btn--delete:hover svg{stroke:#f43f5e}.osl-card-action-btn--more svg{fill:#9ca3af;stroke:none}.osl-card-action-btn--more:hover{background:#fff;border-color:var(--card-accent, #6366f1);box-shadow:0 2px 8px #0000001f;transform:scale(1.1)}.osl-card-action-btn--more:hover svg{fill:var(--card-accent, #6366f1)}.osl-card-body{padding:8px 12px 10px 14px}.osl-card-field{padding:4px 8px 2px 0;min-width:0;overflow:hidden}.osl-card-label{display:block;font-size:9.5px;font-weight:600;color:#9ca3af;text-transform:uppercase;letter-spacing:.06em;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-bottom:1px}.osl-card-value{display:block;font-size:12.5px;font-weight:500;color:#374151;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.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}@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:13px;width:60%}.osl-sk-line--label{width:40%;height:9px;margin-bottom:4px}.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}.osl-grid-pagination{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:12px 16px;border:1px solid var(--osl-border-color, #e5e7eb);border-radius:10px;background:#f9fafb;flex-wrap:wrap;margin-top:4px}.osl-grid-pagination__info{font-size:12px;color:#6b7280;white-space:nowrap;min-width:120px}.osl-grid-pagination__controls{display:flex;align-items:center;gap:3px;flex-wrap:nowrap}.osl-grid-pagination__size{display:flex;align-items:center;min-width:120px;justify-content:flex-end}.osl-grid-page-btn{display:inline-flex;align-items:center;justify-content:center;height:30px;padding:0 10px;border:1px solid var(--osl-border-color, #e5e7eb);border-radius:var(--osl-border-radius, 6px);background:#fff;color:#374151;font-size:13px;font-family:inherit;cursor:pointer;transition:background .12s,border-color .12s,color .12s;white-space:nowrap}.osl-grid-page-btn:hover:not(:disabled){background:#f3f4f6;border-color:#9ca3af}.osl-grid-page-btn:disabled{opacity:.35;cursor:not-allowed}.osl-grid-page-btn--nav{font-size:12px;color:#6b7280}.osl-grid-page-btn--num{min-width:30px;padding:0;font-size:13px}.osl-grid-page-btn--active{background:var(--osl-primary, #6366f1);border-color:var(--osl-primary, #6366f1);color:#fff;font-weight:600}.osl-grid-page-btn--active:hover:not(:disabled){background:var(--osl-primary-hover, #4f46e5);border-color:var(--osl-primary-hover, #4f46e5)}.osl-grid-page-ellipsis{display:inline-flex;align-items:center;justify-content:center;width:30px;height:30px;color:#9ca3af;font-size:13px;pointer-events:none}.osl-grid-page-size{height:30px;padding:0 8px;border:1px solid var(--osl-border-color, #e5e7eb);border-radius:var(--osl-border-radius, 6px);background:#fff;color:#374151;font-size:12px;font-family:inherit;cursor:pointer;outline:none}.osl-grid-page-size:focus{border-color:var(--osl-primary, #6366f1)}.title-icon{width:20px}\n"], dependencies: [{ kind: "directive", type: i1$1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i1$2.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1$2.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1$2.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: OslTooltipDirective, selector: "[oslTooltip]", inputs: ["oslTooltip", "oslTooltipPosition", "oslTooltipAnimation", "oslTooltipDisabled", "oslTooltipMaxWidth"] }, { 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$1.DatePipe, name: "date" }] });
4063
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: OslSetup, isStandalone: false, selector: "osl-setup", inputs: { title: "title", titleIcon: "titleIcon", columns: "columns", datasource: "datasource", isPaginated: "isPaginated", pageSize: "pageSize", autoMode: "autoMode", tableHeight: "tableHeight", totalRecords: "totalRecords", loading: "loading", dialogWidth: "dialogWidth", isDraggable: "isDraggable", formElements: "formElements", beforeDisplay: "beforeDisplay", onAddEditFn: "onAddEditFn", isLister: "isLister", canAdd: "canAdd", canEdit: "canEdit", canDelete: "canDelete", moreMenuActions: "moreMenuActions", moreMenuHeaderActions: "moreMenuHeaderActions", customFormFooter: "customFormFooter", customHeaderTemp: "customHeaderTemp", partialCustomHeaderTemp: "partialCustomHeaderTemp", stateKey: "stateKey", primaryKey: "primaryKey", onSave: "onSave", cardPageSize: "cardPageSize", cardTemplate: "cardTemplate", cardCol: "cardCol" }, 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: "cardGridRef", first: true, predicate: ["cardGridRef"], 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 <div class=\"d-flex align-items-center\">\n @if(titleIcon){\n\n <span class=\"title-icon mx-2 mb-1\" [innerHTML]=\"renderIcon(titleIcon)\"></span>\n }\n <h5 class=\"mb-0\">{{ title }}</h5>\n\n </div>\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\n @if (moreMenuHeaderActions.length > 0 && hasVisibleHeaderActions) {\n <button class=\"osl-card-action-btn osl-card-action-btn--more\" (click)=\"toggleHeaderMenu($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 </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 && cardPagedData.length === 0) {\n <div class=\"osl-card-grid my-2\">\n @for (sk of skeletonCardRows; track $index) {\n <div class=\"osl-card osl-card--skeleton\">\n <div class=\"osl-card-header osl-card-header--skeleton\">\n <div class=\"osl-card-title-wrap\">\n <div class=\"osl-sk-line osl-sk-line--title\"></div>\n </div>\n </div>\n <div class=\"osl-card-body\">\n <div class=\"row g-0\">\n <div class=\"col-6 osl-card-field\"><div class=\"osl-sk-line osl-sk-line--label\"></div><div class=\"osl-sk-line\"></div></div>\n <div class=\"col-6 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=\"col-6 osl-card-field\"><div class=\"osl-sk-line osl-sk-line--label\"></div><div class=\"osl-sk-line\"></div></div>\n <div class=\"col-6 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>\n </div>\n </div>\n }\n </div>\n }\n\n <!-- Empty state -->\n @else if (cardPagedData.length === 0 && !loading) {\n <div class=\"osl-card-empty my-2\">\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 #cardGridRef class=\"osl-card-grid my-2\" [style.height]=\"tableHeight\">\n @for (row of cardPagedData; track row[primaryKey] ?? $index; let i = $index) {\n\n @if (cardTemplate) {\n <ng-container *ngTemplateOutlet=\"cardTemplate; context: { $implicit: row, index: i }\"></ng-container>\n } @else {\n <div class=\"osl-card my-2\"\n [class.osl-card--selectable]=\"isLister\"\n [class.osl-card--highlighted]=\"isHighlightedCard(row)\"\n (click)=\"isLister ? onRowClick.emit(row) : null\">\n\n <!-- Card Header -->\n <div class=\"osl-card-header\">\n <!-- <svg class=\"osl-card-watermark\" viewBox=\"0 0 120 64\" fill=\"currentColor\" aria-hidden=\"true\">\n <path d=\"M10 42 L18 52 L102 52 L110 42 Z\"/>\n <rect x=\"20\" y=\"34\" width=\"80\" height=\"9\" rx=\"1\"/>\n <rect x=\"26\" y=\"20\" width=\"16\" height=\"15\" rx=\"1\"/>\n <rect x=\"46\" y=\"20\" width=\"16\" height=\"15\" rx=\"1\"/>\n <rect x=\"66\" y=\"20\" width=\"16\" height=\"15\" rx=\"1\"/>\n <rect x=\"85\" y=\"14\" width=\"15\" height=\"21\" rx=\"2\"/>\n <rect x=\"90.5\" y=\"7\" width=\"2\" height=\"9\" rx=\"1\"/>\n <path d=\"M4 57 Q18 53 32 57 Q46 61 60 57 Q74 53 88 57 Q102 61 116 57\" stroke=\"currentColor\" stroke-width=\"2\" fill=\"none\" stroke-linecap=\"round\"/>\n </svg> -->\n <div class=\"osl-card-title-wrap\">\n <span class=\"osl-card-title\"\n >\n @if (cardTitleColumn) {\n <span\n >\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 </span>\n }\n </span>\n </div>\n @if (!isLister && ((hasForm && (canEdit || canDelete)) || (moreMenuActions.length > 0 && hasVisibleActions(row)))) {\n <div class=\"osl-card-actions\">\n @if (hasForm && canEdit) {\n <button class=\"osl-card-action-btn osl-card-action-btn--edit\"\n (click)=\"$event.stopPropagation(); openEditDialog(row)\"\n [oslTooltip]=\"'Edit'\" oslTooltipPosition=\"bottom\">\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 @if (hasForm && canDelete) {\n <button class=\"osl-card-action-btn osl-card-action-btn--delete\"\n (click)=\"$event.stopPropagation(); onDeleteClick(row)\"\n [oslTooltip]=\"'Delete'\" oslTooltipPosition=\"bottom\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" xmlns=\"http://www.w3.org/2000/svg\">\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 <line x1=\"10\" y1=\"11\" x2=\"10\" y2=\"17\"/><line x1=\"14\" y1=\"11\" x2=\"14\" y2=\"17\"/>\n </svg>\n </button>\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)\"\n [oslTooltip]=\"'More actions'\" oslTooltipPosition=\"bottom\">\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 </div>\n }\n </div>\n\n <!-- Card Body -->\n @if (cardBodyColumns.length > 0) {\n <div class=\"osl-card-body\">\n <div class=\"row g-0\">\n @for (col of cardBodyColumns; track col.key) {\n <div [class]=\"'col-' + cardCol + ' 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\n <span>\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 </span>\n </div>\n }\n </div>\n </div>\n }\n\n </div>\n }\n }\n </div>\n }\n\n <!-- Card Paginator -->\n @if (isPaginated && _cardTotal > 0) {\n <div class=\"osl-grid-pagination\">\n <span class=\"osl-grid-pagination__info\">\n @if (loading) {\n Loading...\n } @else {\n {{ cardStartRecord }}\u2013{{ cardEndRecord }} of {{ _cardTotal }} records\n }\n </span>\n\n <div class=\"osl-grid-pagination__controls\">\n <button class=\"osl-grid-page-btn osl-grid-page-btn--nav\"\n (click)=\"cardGoToPage(1)\"\n [disabled]=\"cardCurrentPage === 1 || loading\" title=\"First page\">\u00AB</button>\n <button class=\"osl-grid-page-btn osl-grid-page-btn--nav\"\n (click)=\"cardGoToPage(cardCurrentPage - 1)\"\n [disabled]=\"cardCurrentPage === 1 || loading\" title=\"Previous page\">\u2039 Prev</button>\n\n @for (page of cardPageNumbers; track $index) {\n @if (page === -1) {\n <span class=\"osl-grid-page-ellipsis\">\u2026</span>\n } @else {\n <button class=\"osl-grid-page-btn osl-grid-page-btn--num\"\n [class.osl-grid-page-btn--active]=\"page === cardCurrentPage\"\n [disabled]=\"loading\"\n (click)=\"cardGoToPage(page)\">{{ page }}</button>\n }\n }\n\n <button class=\"osl-grid-page-btn osl-grid-page-btn--nav\"\n (click)=\"cardGoToPage(cardCurrentPage + 1)\"\n [disabled]=\"cardCurrentPage === cardTotalPages || loading\" title=\"Next page\">Next \u203A</button>\n <button class=\"osl-grid-page-btn osl-grid-page-btn--nav\"\n (click)=\"cardGoToPage(cardTotalPages)\"\n [disabled]=\"cardCurrentPage === cardTotalPages || loading\" title=\"Last page\">\u00BB</button>\n </div>\n\n <div class=\"osl-grid-pagination__size\">\n <select class=\"osl-grid-page-size\"\n [ngModel]=\"_effectiveCardPageSize\"\n (ngModelChange)=\"cardOnPageSizeChange($event)\"\n [disabled]=\"loading\">\n @for (opt of cardPageSizeOptions; track opt) {\n <option [value]=\"opt\">{{ opt }} per page</option>\n }\n </select>\n </div>\n </div>\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 header more-actions menu -->\n@if (headerMenuOpen && moreMenuHeaderActions.length > 0) {\n <div class=\"osl-card-menu\"\n [style.top.px]=\"headerMenuPosition.top\"\n [style.left.px]=\"headerMenuPosition.left\"\n (click)=\"$event.stopPropagation()\">\n <div class=\"osl-card-menu-header\">Actions</div>\n @for (action of moreMenuHeaderActions; track $index) {\n @if (!action.hideIf || !action.hideIf(undefined)) {\n <button class=\"osl-card-menu-item\" (click)=\"action.click(undefined); headerMenuOpen = false\">\n <span class=\"osl-card-menu-dot\"></span>\n {{ action.labelIf ? action.labelIf(undefined) : action.label }}\n </button>\n }\n }\n </div>\n}\n\n<!-- Floating card more-actions menu -->\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(cardPagedData[cardOpenMenuIndex])) {\n <button class=\"osl-card-menu-item\"\n (click)=\"action.click(cardPagedData[cardOpenMenuIndex]); cardOpenMenuIndex = null\">\n <span class=\"osl-card-menu-dot\"></span>\n {{ action.labelIf ? action.labelIf(cardPagedData[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: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: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-grid{display:flexbox;width:100%;overflow:auto}@keyframes osl-card-enter{0%{opacity:0;transform:translateY(8px)}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:12px;border:1px solid #eaecf0;box-shadow:0 1px 3px #1018280f,0 1px 2px #1018280a;overflow:hidden;transition:transform .2s cubic-bezier(.34,1.56,.64,1),box-shadow .2s ease,border-color .2s ease;animation:osl-card-enter .26s ease both}.osl-card:hover{transform:translateY(-2px);box-shadow:0 8px 24px #1018281a,0 2px 6px #1018280f}.osl-card--selectable{cursor:pointer}.osl-card--highlighted{border-color:var(--card-accent, #6366f1);box-shadow:0 0 0 3px color-mix(in srgb,var(--card-accent, #6366f1) 20%,transparent),0 4px 16px #1018281a}.osl-card--skeleton{pointer-events:none;animation:osl-card-enter .26s ease both}.osl-card--skeleton:hover{transform:none;box-shadow:0 1px 3px #1018280f}@keyframes osl-card-highlight-pulse{0%{box-shadow:0 0 0 4px color-mix(in srgb,var(--card-accent, #6366f1) 30%,transparent)}to{box-shadow:none}}.osl-card-header{position:relative;overflow:hidden;display:flex;align-items:center;gap:10px;padding:9px 12px 9px 14px;border-bottom:1px solid rgba(0,0,0,.05);min-height:42px}.osl-card-header--skeleton{background:#f9fafb}.osl-card-watermark{position:absolute;right:8px;top:50%;transform:translateY(-50%);width:72px;height:40px;color:var(--card-accent, var(--osl-primary, #6366f1));opacity:.1;pointer-events:none;flex-shrink:0}.osl-card-title-wrap{flex:1;min-width:0;position:relative;z-index:1}.osl-card-title{display:block;font-weight:700;font-size:13px;color:#1e293b;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;line-height:1.4}.osl-card-actions{display:flex;align-items:center;gap:4px;flex-shrink:0;position:relative;z-index:1}.osl-card-action-btn{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;border-radius:7px;border:1.5px solid rgba(255,255,255,.9);background:#fffc;cursor:pointer;transition:background .15s,border-color .15s,transform .15s,box-shadow .15s;padding:0}.osl-card-action-btn svg{width:13px;height:13px;transition:stroke .15s,fill .15s}.osl-card-action-btn--edit svg{stroke:#6b7280}.osl-card-action-btn--edit:hover{background:#fff;border-color:var(--card-accent, #6366f1);box-shadow:0 2px 8px #0000001f;transform:scale(1.1)}.osl-card-action-btn--edit:hover svg{stroke:var(--card-accent, #6366f1)}.osl-card-action-btn--delete svg{stroke:#9ca3af}.osl-card-action-btn--delete:hover{background:#fff1f2;border-color:#f43f5e;box-shadow:0 2px 8px #f43f5e33;transform:scale(1.1)}.osl-card-action-btn--delete:hover svg{stroke:#f43f5e}.osl-card-action-btn--more svg{fill:#9ca3af;stroke:none}.osl-card-action-btn--more:hover{background:#fff;border-color:var(--card-accent, #6366f1);box-shadow:0 2px 8px #0000001f;transform:scale(1.1)}.osl-card-action-btn--more:hover svg{fill:var(--card-accent, #6366f1)}.osl-card-body{padding:8px 12px 10px 14px}.osl-card-field{padding:4px 8px 2px 0;min-width:0;overflow:hidden}.osl-card-label{display:block;font-size:9.5px;font-weight:600;color:#9ca3af;text-transform:uppercase;letter-spacing:.06em;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-bottom:1px}.osl-card-value{display:block;font-size:12.5px;font-weight:500;color:#374151;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.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}@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:13px;width:60%}.osl-sk-line--label{width:40%;height:9px;margin-bottom:4px}.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}.osl-grid-pagination{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:12px 16px;border:1px solid var(--osl-border-color, #e5e7eb);border-radius:10px;background:#f9fafb;flex-wrap:wrap;margin-top:4px}.osl-grid-pagination__info{font-size:12px;color:#6b7280;white-space:nowrap;min-width:120px}.osl-grid-pagination__controls{display:flex;align-items:center;gap:3px;flex-wrap:nowrap}.osl-grid-pagination__size{display:flex;align-items:center;min-width:120px;justify-content:flex-end}.osl-grid-page-btn{display:inline-flex;align-items:center;justify-content:center;height:30px;padding:0 10px;border:1px solid var(--osl-border-color, #e5e7eb);border-radius:var(--osl-border-radius, 6px);background:#fff;color:#374151;font-size:13px;font-family:inherit;cursor:pointer;transition:background .12s,border-color .12s,color .12s;white-space:nowrap}.osl-grid-page-btn:hover:not(:disabled){background:#f3f4f6;border-color:#9ca3af}.osl-grid-page-btn:disabled{opacity:.35;cursor:not-allowed}.osl-grid-page-btn--nav{font-size:12px;color:#6b7280}.osl-grid-page-btn--num{min-width:30px;padding:0;font-size:13px}.osl-grid-page-btn--active{background:var(--osl-primary, #6366f1);border-color:var(--osl-primary, #6366f1);color:#fff;font-weight:600}.osl-grid-page-btn--active:hover:not(:disabled){background:var(--osl-primary-hover, #4f46e5);border-color:var(--osl-primary-hover, #4f46e5)}.osl-grid-page-ellipsis{display:inline-flex;align-items:center;justify-content:center;width:30px;height:30px;color:#9ca3af;font-size:13px;pointer-events:none}.osl-grid-page-size{height:30px;padding:0 8px;border:1px solid var(--osl-border-color, #e5e7eb);border-radius:var(--osl-border-radius, 6px);background:#fff;color:#374151;font-size:12px;font-family:inherit;cursor:pointer;outline:none}.osl-grid-page-size:focus{border-color:var(--osl-primary, #6366f1)}.title-icon{width:20px}\n"], dependencies: [{ kind: "directive", type: i1$1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i1$2.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1$2.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1$2.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: OslTooltipDirective, selector: "[oslTooltip]", inputs: ["oslTooltip", "oslTooltipPosition", "oslTooltipAnimation", "oslTooltipDisabled", "oslTooltipMaxWidth"] }, { 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$1.DatePipe, name: "date" }] });
4032
4064
  }
4033
4065
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: OslSetup, decorators: [{
4034
4066
  type: Component,
4035
- 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 <div class=\"d-flex align-items-center\">\n @if(titleIcon){\n\n <span class=\"title-icon mx-2 mb-1\" [innerHTML]=\"renderIcon(titleIcon)\"></span>\n }\n <h5 class=\"mb-0\">{{ title }}</h5>\n\n </div>\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 && cardPagedData.length === 0) {\n <div class=\"osl-card-grid my-2\">\n @for (sk of skeletonCardRows; track $index) {\n <div class=\"osl-card osl-card--skeleton\">\n <div class=\"osl-card-header osl-card-header--skeleton\">\n <div class=\"osl-card-title-wrap\">\n <div class=\"osl-sk-line osl-sk-line--title\"></div>\n </div>\n </div>\n <div class=\"osl-card-body\">\n <div class=\"row g-0\">\n <div class=\"col-6 osl-card-field\"><div class=\"osl-sk-line osl-sk-line--label\"></div><div class=\"osl-sk-line\"></div></div>\n <div class=\"col-6 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=\"col-6 osl-card-field\"><div class=\"osl-sk-line osl-sk-line--label\"></div><div class=\"osl-sk-line\"></div></div>\n <div class=\"col-6 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>\n </div>\n </div>\n }\n </div>\n }\n\n <!-- Empty state -->\n @else if (cardPagedData.length === 0 && !loading) {\n <div class=\"osl-card-empty my-2\">\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 #cardGridRef class=\"osl-card-grid my-2\" [style.height]=\"tableHeight\">\n @for (row of cardPagedData; track row[primaryKey] ?? $index; let i = $index) {\n\n @if (cardTemplate) {\n <ng-container *ngTemplateOutlet=\"cardTemplate; context: { $implicit: row, index: i }\"></ng-container>\n } @else {\n <div class=\"osl-card my-2\"\n [class.osl-card--selectable]=\"isLister\"\n [class.osl-card--highlighted]=\"isHighlightedCard(row)\"\n (click)=\"isLister ? onRowClick.emit(row) : null\">\n\n <!-- Card Header -->\n <div class=\"osl-card-header\">\n <!-- <svg class=\"osl-card-watermark\" viewBox=\"0 0 120 64\" fill=\"currentColor\" aria-hidden=\"true\">\n <path d=\"M10 42 L18 52 L102 52 L110 42 Z\"/>\n <rect x=\"20\" y=\"34\" width=\"80\" height=\"9\" rx=\"1\"/>\n <rect x=\"26\" y=\"20\" width=\"16\" height=\"15\" rx=\"1\"/>\n <rect x=\"46\" y=\"20\" width=\"16\" height=\"15\" rx=\"1\"/>\n <rect x=\"66\" y=\"20\" width=\"16\" height=\"15\" rx=\"1\"/>\n <rect x=\"85\" y=\"14\" width=\"15\" height=\"21\" rx=\"2\"/>\n <rect x=\"90.5\" y=\"7\" width=\"2\" height=\"9\" rx=\"1\"/>\n <path d=\"M4 57 Q18 53 32 57 Q46 61 60 57 Q74 53 88 57 Q102 61 116 57\" stroke=\"currentColor\" stroke-width=\"2\" fill=\"none\" stroke-linecap=\"round\"/>\n </svg> -->\n <div class=\"osl-card-title-wrap\">\n <span class=\"osl-card-title\"\n >\n @if (cardTitleColumn) {\n <span\n >\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 </span>\n }\n </span>\n </div>\n @if (!isLister && ((hasForm && (canEdit || canDelete)) || (moreMenuActions.length > 0 && hasVisibleActions(row)))) {\n <div class=\"osl-card-actions\">\n @if (hasForm && canEdit) {\n <button class=\"osl-card-action-btn osl-card-action-btn--edit\"\n (click)=\"$event.stopPropagation(); openEditDialog(row)\"\n [oslTooltip]=\"'Edit'\" oslTooltipPosition=\"bottom\">\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 @if (hasForm && canDelete) {\n <button class=\"osl-card-action-btn osl-card-action-btn--delete\"\n (click)=\"$event.stopPropagation(); onDeleteClick(row)\"\n [oslTooltip]=\"'Delete'\" oslTooltipPosition=\"bottom\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" xmlns=\"http://www.w3.org/2000/svg\">\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 <line x1=\"10\" y1=\"11\" x2=\"10\" y2=\"17\"/><line x1=\"14\" y1=\"11\" x2=\"14\" y2=\"17\"/>\n </svg>\n </button>\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)\"\n [oslTooltip]=\"'More actions'\" oslTooltipPosition=\"bottom\">\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 </div>\n }\n </div>\n\n <!-- Card Body -->\n @if (cardBodyColumns.length > 0) {\n <div class=\"osl-card-body\">\n <div class=\"row g-0\">\n @for (col of cardBodyColumns; track col.key) {\n <div [class]=\"'col-' + cardCol + ' 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\n <span>\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 </span>\n </div>\n }\n </div>\n </div>\n }\n\n </div>\n }\n }\n </div>\n }\n\n <!-- Card Paginator -->\n @if (isPaginated && _cardTotal > 0) {\n <div class=\"osl-grid-pagination\">\n <span class=\"osl-grid-pagination__info\">\n @if (loading) {\n Loading...\n } @else {\n {{ cardStartRecord }}\u2013{{ cardEndRecord }} of {{ _cardTotal }} records\n }\n </span>\n\n <div class=\"osl-grid-pagination__controls\">\n <button class=\"osl-grid-page-btn osl-grid-page-btn--nav\"\n (click)=\"cardGoToPage(1)\"\n [disabled]=\"cardCurrentPage === 1 || loading\" title=\"First page\">\u00AB</button>\n <button class=\"osl-grid-page-btn osl-grid-page-btn--nav\"\n (click)=\"cardGoToPage(cardCurrentPage - 1)\"\n [disabled]=\"cardCurrentPage === 1 || loading\" title=\"Previous page\">\u2039 Prev</button>\n\n @for (page of cardPageNumbers; track $index) {\n @if (page === -1) {\n <span class=\"osl-grid-page-ellipsis\">\u2026</span>\n } @else {\n <button class=\"osl-grid-page-btn osl-grid-page-btn--num\"\n [class.osl-grid-page-btn--active]=\"page === cardCurrentPage\"\n [disabled]=\"loading\"\n (click)=\"cardGoToPage(page)\">{{ page }}</button>\n }\n }\n\n <button class=\"osl-grid-page-btn osl-grid-page-btn--nav\"\n (click)=\"cardGoToPage(cardCurrentPage + 1)\"\n [disabled]=\"cardCurrentPage === cardTotalPages || loading\" title=\"Next page\">Next \u203A</button>\n <button class=\"osl-grid-page-btn osl-grid-page-btn--nav\"\n (click)=\"cardGoToPage(cardTotalPages)\"\n [disabled]=\"cardCurrentPage === cardTotalPages || loading\" title=\"Last page\">\u00BB</button>\n </div>\n\n <div class=\"osl-grid-pagination__size\">\n <select class=\"osl-grid-page-size\"\n [ngModel]=\"_effectiveCardPageSize\"\n (ngModelChange)=\"cardOnPageSizeChange($event)\"\n [disabled]=\"loading\">\n @for (opt of cardPageSizeOptions; track opt) {\n <option [value]=\"opt\">{{ opt }} per page</option>\n }\n </select>\n </div>\n </div>\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 -->\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(cardPagedData[cardOpenMenuIndex])) {\n <button class=\"osl-card-menu-item\"\n (click)=\"action.click(cardPagedData[cardOpenMenuIndex]); cardOpenMenuIndex = null\">\n <span class=\"osl-card-menu-dot\"></span>\n {{ action.labelIf ? action.labelIf(cardPagedData[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: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: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-grid{display:flexbox;width:100%;overflow:auto}@keyframes osl-card-enter{0%{opacity:0;transform:translateY(8px)}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:12px;border:1px solid #eaecf0;box-shadow:0 1px 3px #1018280f,0 1px 2px #1018280a;overflow:hidden;transition:transform .2s cubic-bezier(.34,1.56,.64,1),box-shadow .2s ease,border-color .2s ease;animation:osl-card-enter .26s ease both}.osl-card:hover{transform:translateY(-2px);box-shadow:0 8px 24px #1018281a,0 2px 6px #1018280f}.osl-card--selectable{cursor:pointer}.osl-card--highlighted{border-color:var(--card-accent, #6366f1);box-shadow:0 0 0 3px color-mix(in srgb,var(--card-accent, #6366f1) 20%,transparent),0 4px 16px #1018281a}.osl-card--skeleton{pointer-events:none;animation:osl-card-enter .26s ease both}.osl-card--skeleton:hover{transform:none;box-shadow:0 1px 3px #1018280f}@keyframes osl-card-highlight-pulse{0%{box-shadow:0 0 0 4px color-mix(in srgb,var(--card-accent, #6366f1) 30%,transparent)}to{box-shadow:none}}.osl-card-header{position:relative;overflow:hidden;display:flex;align-items:center;gap:10px;padding:9px 12px 9px 14px;border-bottom:1px solid rgba(0,0,0,.05);min-height:42px}.osl-card-header--skeleton{background:#f9fafb}.osl-card-watermark{position:absolute;right:8px;top:50%;transform:translateY(-50%);width:72px;height:40px;color:var(--card-accent, var(--osl-primary, #6366f1));opacity:.1;pointer-events:none;flex-shrink:0}.osl-card-title-wrap{flex:1;min-width:0;position:relative;z-index:1}.osl-card-title{display:block;font-weight:700;font-size:13px;color:#1e293b;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;line-height:1.4}.osl-card-actions{display:flex;align-items:center;gap:4px;flex-shrink:0;position:relative;z-index:1}.osl-card-action-btn{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;border-radius:7px;border:1.5px solid rgba(255,255,255,.9);background:#fffc;cursor:pointer;transition:background .15s,border-color .15s,transform .15s,box-shadow .15s;padding:0}.osl-card-action-btn svg{width:13px;height:13px;transition:stroke .15s,fill .15s}.osl-card-action-btn--edit svg{stroke:#6b7280}.osl-card-action-btn--edit:hover{background:#fff;border-color:var(--card-accent, #6366f1);box-shadow:0 2px 8px #0000001f;transform:scale(1.1)}.osl-card-action-btn--edit:hover svg{stroke:var(--card-accent, #6366f1)}.osl-card-action-btn--delete svg{stroke:#9ca3af}.osl-card-action-btn--delete:hover{background:#fff1f2;border-color:#f43f5e;box-shadow:0 2px 8px #f43f5e33;transform:scale(1.1)}.osl-card-action-btn--delete:hover svg{stroke:#f43f5e}.osl-card-action-btn--more svg{fill:#9ca3af;stroke:none}.osl-card-action-btn--more:hover{background:#fff;border-color:var(--card-accent, #6366f1);box-shadow:0 2px 8px #0000001f;transform:scale(1.1)}.osl-card-action-btn--more:hover svg{fill:var(--card-accent, #6366f1)}.osl-card-body{padding:8px 12px 10px 14px}.osl-card-field{padding:4px 8px 2px 0;min-width:0;overflow:hidden}.osl-card-label{display:block;font-size:9.5px;font-weight:600;color:#9ca3af;text-transform:uppercase;letter-spacing:.06em;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-bottom:1px}.osl-card-value{display:block;font-size:12.5px;font-weight:500;color:#374151;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.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}@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:13px;width:60%}.osl-sk-line--label{width:40%;height:9px;margin-bottom:4px}.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}.osl-grid-pagination{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:12px 16px;border:1px solid var(--osl-border-color, #e5e7eb);border-radius:10px;background:#f9fafb;flex-wrap:wrap;margin-top:4px}.osl-grid-pagination__info{font-size:12px;color:#6b7280;white-space:nowrap;min-width:120px}.osl-grid-pagination__controls{display:flex;align-items:center;gap:3px;flex-wrap:nowrap}.osl-grid-pagination__size{display:flex;align-items:center;min-width:120px;justify-content:flex-end}.osl-grid-page-btn{display:inline-flex;align-items:center;justify-content:center;height:30px;padding:0 10px;border:1px solid var(--osl-border-color, #e5e7eb);border-radius:var(--osl-border-radius, 6px);background:#fff;color:#374151;font-size:13px;font-family:inherit;cursor:pointer;transition:background .12s,border-color .12s,color .12s;white-space:nowrap}.osl-grid-page-btn:hover:not(:disabled){background:#f3f4f6;border-color:#9ca3af}.osl-grid-page-btn:disabled{opacity:.35;cursor:not-allowed}.osl-grid-page-btn--nav{font-size:12px;color:#6b7280}.osl-grid-page-btn--num{min-width:30px;padding:0;font-size:13px}.osl-grid-page-btn--active{background:var(--osl-primary, #6366f1);border-color:var(--osl-primary, #6366f1);color:#fff;font-weight:600}.osl-grid-page-btn--active:hover:not(:disabled){background:var(--osl-primary-hover, #4f46e5);border-color:var(--osl-primary-hover, #4f46e5)}.osl-grid-page-ellipsis{display:inline-flex;align-items:center;justify-content:center;width:30px;height:30px;color:#9ca3af;font-size:13px;pointer-events:none}.osl-grid-page-size{height:30px;padding:0 8px;border:1px solid var(--osl-border-color, #e5e7eb);border-radius:var(--osl-border-radius, 6px);background:#fff;color:#374151;font-size:12px;font-family:inherit;cursor:pointer;outline:none}.osl-grid-page-size:focus{border-color:var(--osl-primary, #6366f1)}.title-icon{width:20px}\n"] }]
4067
+ 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 <div class=\"d-flex align-items-center\">\n @if(titleIcon){\n\n <span class=\"title-icon mx-2 mb-1\" [innerHTML]=\"renderIcon(titleIcon)\"></span>\n }\n <h5 class=\"mb-0\">{{ title }}</h5>\n\n </div>\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\n @if (moreMenuHeaderActions.length > 0 && hasVisibleHeaderActions) {\n <button class=\"osl-card-action-btn osl-card-action-btn--more\" (click)=\"toggleHeaderMenu($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 </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 && cardPagedData.length === 0) {\n <div class=\"osl-card-grid my-2\">\n @for (sk of skeletonCardRows; track $index) {\n <div class=\"osl-card osl-card--skeleton\">\n <div class=\"osl-card-header osl-card-header--skeleton\">\n <div class=\"osl-card-title-wrap\">\n <div class=\"osl-sk-line osl-sk-line--title\"></div>\n </div>\n </div>\n <div class=\"osl-card-body\">\n <div class=\"row g-0\">\n <div class=\"col-6 osl-card-field\"><div class=\"osl-sk-line osl-sk-line--label\"></div><div class=\"osl-sk-line\"></div></div>\n <div class=\"col-6 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=\"col-6 osl-card-field\"><div class=\"osl-sk-line osl-sk-line--label\"></div><div class=\"osl-sk-line\"></div></div>\n <div class=\"col-6 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>\n </div>\n </div>\n }\n </div>\n }\n\n <!-- Empty state -->\n @else if (cardPagedData.length === 0 && !loading) {\n <div class=\"osl-card-empty my-2\">\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 #cardGridRef class=\"osl-card-grid my-2\" [style.height]=\"tableHeight\">\n @for (row of cardPagedData; track row[primaryKey] ?? $index; let i = $index) {\n\n @if (cardTemplate) {\n <ng-container *ngTemplateOutlet=\"cardTemplate; context: { $implicit: row, index: i }\"></ng-container>\n } @else {\n <div class=\"osl-card my-2\"\n [class.osl-card--selectable]=\"isLister\"\n [class.osl-card--highlighted]=\"isHighlightedCard(row)\"\n (click)=\"isLister ? onRowClick.emit(row) : null\">\n\n <!-- Card Header -->\n <div class=\"osl-card-header\">\n <!-- <svg class=\"osl-card-watermark\" viewBox=\"0 0 120 64\" fill=\"currentColor\" aria-hidden=\"true\">\n <path d=\"M10 42 L18 52 L102 52 L110 42 Z\"/>\n <rect x=\"20\" y=\"34\" width=\"80\" height=\"9\" rx=\"1\"/>\n <rect x=\"26\" y=\"20\" width=\"16\" height=\"15\" rx=\"1\"/>\n <rect x=\"46\" y=\"20\" width=\"16\" height=\"15\" rx=\"1\"/>\n <rect x=\"66\" y=\"20\" width=\"16\" height=\"15\" rx=\"1\"/>\n <rect x=\"85\" y=\"14\" width=\"15\" height=\"21\" rx=\"2\"/>\n <rect x=\"90.5\" y=\"7\" width=\"2\" height=\"9\" rx=\"1\"/>\n <path d=\"M4 57 Q18 53 32 57 Q46 61 60 57 Q74 53 88 57 Q102 61 116 57\" stroke=\"currentColor\" stroke-width=\"2\" fill=\"none\" stroke-linecap=\"round\"/>\n </svg> -->\n <div class=\"osl-card-title-wrap\">\n <span class=\"osl-card-title\"\n >\n @if (cardTitleColumn) {\n <span\n >\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 </span>\n }\n </span>\n </div>\n @if (!isLister && ((hasForm && (canEdit || canDelete)) || (moreMenuActions.length > 0 && hasVisibleActions(row)))) {\n <div class=\"osl-card-actions\">\n @if (hasForm && canEdit) {\n <button class=\"osl-card-action-btn osl-card-action-btn--edit\"\n (click)=\"$event.stopPropagation(); openEditDialog(row)\"\n [oslTooltip]=\"'Edit'\" oslTooltipPosition=\"bottom\">\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 @if (hasForm && canDelete) {\n <button class=\"osl-card-action-btn osl-card-action-btn--delete\"\n (click)=\"$event.stopPropagation(); onDeleteClick(row)\"\n [oslTooltip]=\"'Delete'\" oslTooltipPosition=\"bottom\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" xmlns=\"http://www.w3.org/2000/svg\">\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 <line x1=\"10\" y1=\"11\" x2=\"10\" y2=\"17\"/><line x1=\"14\" y1=\"11\" x2=\"14\" y2=\"17\"/>\n </svg>\n </button>\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)\"\n [oslTooltip]=\"'More actions'\" oslTooltipPosition=\"bottom\">\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 </div>\n }\n </div>\n\n <!-- Card Body -->\n @if (cardBodyColumns.length > 0) {\n <div class=\"osl-card-body\">\n <div class=\"row g-0\">\n @for (col of cardBodyColumns; track col.key) {\n <div [class]=\"'col-' + cardCol + ' 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\n <span>\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 </span>\n </div>\n }\n </div>\n </div>\n }\n\n </div>\n }\n }\n </div>\n }\n\n <!-- Card Paginator -->\n @if (isPaginated && _cardTotal > 0) {\n <div class=\"osl-grid-pagination\">\n <span class=\"osl-grid-pagination__info\">\n @if (loading) {\n Loading...\n } @else {\n {{ cardStartRecord }}\u2013{{ cardEndRecord }} of {{ _cardTotal }} records\n }\n </span>\n\n <div class=\"osl-grid-pagination__controls\">\n <button class=\"osl-grid-page-btn osl-grid-page-btn--nav\"\n (click)=\"cardGoToPage(1)\"\n [disabled]=\"cardCurrentPage === 1 || loading\" title=\"First page\">\u00AB</button>\n <button class=\"osl-grid-page-btn osl-grid-page-btn--nav\"\n (click)=\"cardGoToPage(cardCurrentPage - 1)\"\n [disabled]=\"cardCurrentPage === 1 || loading\" title=\"Previous page\">\u2039 Prev</button>\n\n @for (page of cardPageNumbers; track $index) {\n @if (page === -1) {\n <span class=\"osl-grid-page-ellipsis\">\u2026</span>\n } @else {\n <button class=\"osl-grid-page-btn osl-grid-page-btn--num\"\n [class.osl-grid-page-btn--active]=\"page === cardCurrentPage\"\n [disabled]=\"loading\"\n (click)=\"cardGoToPage(page)\">{{ page }}</button>\n }\n }\n\n <button class=\"osl-grid-page-btn osl-grid-page-btn--nav\"\n (click)=\"cardGoToPage(cardCurrentPage + 1)\"\n [disabled]=\"cardCurrentPage === cardTotalPages || loading\" title=\"Next page\">Next \u203A</button>\n <button class=\"osl-grid-page-btn osl-grid-page-btn--nav\"\n (click)=\"cardGoToPage(cardTotalPages)\"\n [disabled]=\"cardCurrentPage === cardTotalPages || loading\" title=\"Last page\">\u00BB</button>\n </div>\n\n <div class=\"osl-grid-pagination__size\">\n <select class=\"osl-grid-page-size\"\n [ngModel]=\"_effectiveCardPageSize\"\n (ngModelChange)=\"cardOnPageSizeChange($event)\"\n [disabled]=\"loading\">\n @for (opt of cardPageSizeOptions; track opt) {\n <option [value]=\"opt\">{{ opt }} per page</option>\n }\n </select>\n </div>\n </div>\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 header more-actions menu -->\n@if (headerMenuOpen && moreMenuHeaderActions.length > 0) {\n <div class=\"osl-card-menu\"\n [style.top.px]=\"headerMenuPosition.top\"\n [style.left.px]=\"headerMenuPosition.left\"\n (click)=\"$event.stopPropagation()\">\n <div class=\"osl-card-menu-header\">Actions</div>\n @for (action of moreMenuHeaderActions; track $index) {\n @if (!action.hideIf || !action.hideIf(undefined)) {\n <button class=\"osl-card-menu-item\" (click)=\"action.click(undefined); headerMenuOpen = false\">\n <span class=\"osl-card-menu-dot\"></span>\n {{ action.labelIf ? action.labelIf(undefined) : action.label }}\n </button>\n }\n }\n </div>\n}\n\n<!-- Floating card more-actions menu -->\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(cardPagedData[cardOpenMenuIndex])) {\n <button class=\"osl-card-menu-item\"\n (click)=\"action.click(cardPagedData[cardOpenMenuIndex]); cardOpenMenuIndex = null\">\n <span class=\"osl-card-menu-dot\"></span>\n {{ action.labelIf ? action.labelIf(cardPagedData[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: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: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-grid{display:flexbox;width:100%;overflow:auto}@keyframes osl-card-enter{0%{opacity:0;transform:translateY(8px)}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:12px;border:1px solid #eaecf0;box-shadow:0 1px 3px #1018280f,0 1px 2px #1018280a;overflow:hidden;transition:transform .2s cubic-bezier(.34,1.56,.64,1),box-shadow .2s ease,border-color .2s ease;animation:osl-card-enter .26s ease both}.osl-card:hover{transform:translateY(-2px);box-shadow:0 8px 24px #1018281a,0 2px 6px #1018280f}.osl-card--selectable{cursor:pointer}.osl-card--highlighted{border-color:var(--card-accent, #6366f1);box-shadow:0 0 0 3px color-mix(in srgb,var(--card-accent, #6366f1) 20%,transparent),0 4px 16px #1018281a}.osl-card--skeleton{pointer-events:none;animation:osl-card-enter .26s ease both}.osl-card--skeleton:hover{transform:none;box-shadow:0 1px 3px #1018280f}@keyframes osl-card-highlight-pulse{0%{box-shadow:0 0 0 4px color-mix(in srgb,var(--card-accent, #6366f1) 30%,transparent)}to{box-shadow:none}}.osl-card-header{position:relative;overflow:hidden;display:flex;align-items:center;gap:10px;padding:9px 12px 9px 14px;border-bottom:1px solid rgba(0,0,0,.05);min-height:42px}.osl-card-header--skeleton{background:#f9fafb}.osl-card-watermark{position:absolute;right:8px;top:50%;transform:translateY(-50%);width:72px;height:40px;color:var(--card-accent, var(--osl-primary, #6366f1));opacity:.1;pointer-events:none;flex-shrink:0}.osl-card-title-wrap{flex:1;min-width:0;position:relative;z-index:1}.osl-card-title{display:block;font-weight:700;font-size:13px;color:#1e293b;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;line-height:1.4}.osl-card-actions{display:flex;align-items:center;gap:4px;flex-shrink:0;position:relative;z-index:1}.osl-card-action-btn{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;border-radius:7px;border:1.5px solid rgba(255,255,255,.9);background:#fffc;cursor:pointer;transition:background .15s,border-color .15s,transform .15s,box-shadow .15s;padding:0}.osl-card-action-btn svg{width:13px;height:13px;transition:stroke .15s,fill .15s}.osl-card-action-btn--edit svg{stroke:#6b7280}.osl-card-action-btn--edit:hover{background:#fff;border-color:var(--card-accent, #6366f1);box-shadow:0 2px 8px #0000001f;transform:scale(1.1)}.osl-card-action-btn--edit:hover svg{stroke:var(--card-accent, #6366f1)}.osl-card-action-btn--delete svg{stroke:#9ca3af}.osl-card-action-btn--delete:hover{background:#fff1f2;border-color:#f43f5e;box-shadow:0 2px 8px #f43f5e33;transform:scale(1.1)}.osl-card-action-btn--delete:hover svg{stroke:#f43f5e}.osl-card-action-btn--more svg{fill:#9ca3af;stroke:none}.osl-card-action-btn--more:hover{background:#fff;border-color:var(--card-accent, #6366f1);box-shadow:0 2px 8px #0000001f;transform:scale(1.1)}.osl-card-action-btn--more:hover svg{fill:var(--card-accent, #6366f1)}.osl-card-body{padding:8px 12px 10px 14px}.osl-card-field{padding:4px 8px 2px 0;min-width:0;overflow:hidden}.osl-card-label{display:block;font-size:9.5px;font-weight:600;color:#9ca3af;text-transform:uppercase;letter-spacing:.06em;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-bottom:1px}.osl-card-value{display:block;font-size:12.5px;font-weight:500;color:#374151;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.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}@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:13px;width:60%}.osl-sk-line--label{width:40%;height:9px;margin-bottom:4px}.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}.osl-grid-pagination{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:12px 16px;border:1px solid var(--osl-border-color, #e5e7eb);border-radius:10px;background:#f9fafb;flex-wrap:wrap;margin-top:4px}.osl-grid-pagination__info{font-size:12px;color:#6b7280;white-space:nowrap;min-width:120px}.osl-grid-pagination__controls{display:flex;align-items:center;gap:3px;flex-wrap:nowrap}.osl-grid-pagination__size{display:flex;align-items:center;min-width:120px;justify-content:flex-end}.osl-grid-page-btn{display:inline-flex;align-items:center;justify-content:center;height:30px;padding:0 10px;border:1px solid var(--osl-border-color, #e5e7eb);border-radius:var(--osl-border-radius, 6px);background:#fff;color:#374151;font-size:13px;font-family:inherit;cursor:pointer;transition:background .12s,border-color .12s,color .12s;white-space:nowrap}.osl-grid-page-btn:hover:not(:disabled){background:#f3f4f6;border-color:#9ca3af}.osl-grid-page-btn:disabled{opacity:.35;cursor:not-allowed}.osl-grid-page-btn--nav{font-size:12px;color:#6b7280}.osl-grid-page-btn--num{min-width:30px;padding:0;font-size:13px}.osl-grid-page-btn--active{background:var(--osl-primary, #6366f1);border-color:var(--osl-primary, #6366f1);color:#fff;font-weight:600}.osl-grid-page-btn--active:hover:not(:disabled){background:var(--osl-primary-hover, #4f46e5);border-color:var(--osl-primary-hover, #4f46e5)}.osl-grid-page-ellipsis{display:inline-flex;align-items:center;justify-content:center;width:30px;height:30px;color:#9ca3af;font-size:13px;pointer-events:none}.osl-grid-page-size{height:30px;padding:0 8px;border:1px solid var(--osl-border-color, #e5e7eb);border-radius:var(--osl-border-radius, 6px);background:#fff;color:#374151;font-size:12px;font-family:inherit;cursor:pointer;outline:none}.osl-grid-page-size:focus{border-color:var(--osl-primary, #6366f1)}.title-icon{width:20px}\n"] }]
4036
4068
  }], propDecorators: { formBodyTpl: [{
4037
4069
  type: ViewChild,
4038
4070
  args: ['formBodyTpl']
@@ -4111,6 +4143,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
4111
4143
  }], moreMenuActions: [{
4112
4144
  type: Input,
4113
4145
  args: ['moreMenuActions']
4146
+ }], moreMenuHeaderActions: [{
4147
+ type: Input,
4148
+ args: ['moreMenuHeaderActions']
4114
4149
  }], customFormFooter: [{
4115
4150
  type: Input,
4116
4151
  args: ['customFormFooter']
@@ -4633,7 +4668,7 @@ class OslAutocompleteLister {
4633
4668
  this.dialogRef.close(event);
4634
4669
  }
4635
4670
  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 });
4636
- 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", "titleIcon", "columns", "datasource", "isPaginated", "pageSize", "autoMode", "tableHeight", "totalRecords", "loading", "dialogWidth", "isDraggable", "formElements", "beforeDisplay", "onAddEditFn", "isLister", "canAdd", "canEdit", "canDelete", "moreMenuActions", "customFormFooter", "customHeaderTemp", "partialCustomHeaderTemp", "stateKey", "primaryKey", "onSave", "cardPageSize", "cardTemplate", "cardCol"], outputs: ["onSearch", "onAdd", "onEdit", "onDelete", "pageChange", "pageSizeChange", "sortChange", "onRowClick", "onStateRestored"] }] });
4671
+ 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", "titleIcon", "columns", "datasource", "isPaginated", "pageSize", "autoMode", "tableHeight", "totalRecords", "loading", "dialogWidth", "isDraggable", "formElements", "beforeDisplay", "onAddEditFn", "isLister", "canAdd", "canEdit", "canDelete", "moreMenuActions", "moreMenuHeaderActions", "customFormFooter", "customHeaderTemp", "partialCustomHeaderTemp", "stateKey", "primaryKey", "onSave", "cardPageSize", "cardTemplate", "cardCol"], outputs: ["onSearch", "onAdd", "onEdit", "onDelete", "pageChange", "pageSizeChange", "sortChange", "onRowClick", "onStateRestored"] }] });
4637
4672
  }
4638
4673
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: OslAutocompleteLister, decorators: [{
4639
4674
  type: Component,