osl-base-extended 7.0.0 → 7.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -24,8 +24,6 @@ import { MAT_DATE_FORMATS } from '@angular/material/core';
24
24
  import * as i4 from '@ngxmc/datetime-picker';
25
25
  import { NgxMatDatetimepicker, NgxMatDatepickerInput } from '@ngxmc/datetime-picker';
26
26
  import { MatInputModule } from '@angular/material/input';
27
- import * as i2$2 from '@angular/cdk/scrolling';
28
- import { ScrollingModule } from '@angular/cdk/scrolling';
29
27
  import { debounceTime as debounceTime$1, distinctUntilChanged as distinctUntilChanged$1, switchMap, tap } from 'rxjs/operators';
30
28
  import { MatMenuModule } from '@angular/material/menu';
31
29
  import * as i3$1 from '@angular/cdk/drag-drop';
@@ -3063,6 +3061,201 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
3063
3061
  args: [{ providedIn: 'root' }]
3064
3062
  }] });
3065
3063
 
3064
+ const TOOLTIP_CSS = `
3065
+ .osl-tooltip {
3066
+ position: fixed;
3067
+ top: -9999px;
3068
+ left: -9999px;
3069
+ z-index: 10000;
3070
+ background: #111827;
3071
+ color: #ffffff;
3072
+ font-size: 12px;
3073
+ line-height: 1.55;
3074
+ font-family: inherit;
3075
+ padding: 6px 12px;
3076
+ border-radius: 8px;
3077
+ word-break: break-word;
3078
+ white-space: pre-wrap;
3079
+ box-shadow: 0 8px 24px rgba(0,0,0,0.22), 0 2px 6px rgba(0,0,0,0.14);
3080
+ pointer-events: none;
3081
+ }
3082
+ .osl-tooltip::after {
3083
+ content: '';
3084
+ position: absolute;
3085
+ border: 5px solid transparent;
3086
+ }
3087
+ .osl-tooltip--top::after {
3088
+ top: 100%;
3089
+ left: 50%;
3090
+ transform: translateX(-50%);
3091
+ border-top-color: #111827;
3092
+ }
3093
+ .osl-tooltip--bottom::after {
3094
+ bottom: 100%;
3095
+ left: 50%;
3096
+ transform: translateX(-50%);
3097
+ border-bottom-color: #111827;
3098
+ }
3099
+ .osl-tooltip--left::after {
3100
+ top: 50%;
3101
+ left: 100%;
3102
+ transform: translateY(-50%);
3103
+ border-left-color: #111827;
3104
+ }
3105
+ .osl-tooltip--right::after {
3106
+ top: 50%;
3107
+ right: 100%;
3108
+ transform: translateY(-50%);
3109
+ border-right-color: #111827;
3110
+ }
3111
+
3112
+ /* Default: position-aware slide animations */
3113
+ .osl-tooltip--top { animation: _osl_tip_up 0.18s cubic-bezier(0.16,1,0.3,1) forwards; }
3114
+ .osl-tooltip--bottom { animation: _osl_tip_down 0.18s cubic-bezier(0.16,1,0.3,1) forwards; }
3115
+ .osl-tooltip--left { animation: _osl_tip_left 0.18s cubic-bezier(0.16,1,0.3,1) forwards; }
3116
+ .osl-tooltip--right { animation: _osl_tip_right 0.18s cubic-bezier(0.16,1,0.3,1) forwards; }
3117
+
3118
+ /* Animation override variants */
3119
+ .osl-tooltip--anim-scale { animation: _osl_tip_scale 0.20s cubic-bezier(0.34,1.56,0.64,1) forwards !important; }
3120
+ .osl-tooltip--anim-bounce { animation: _osl_tip_bounce 0.40s cubic-bezier(0.34,1.56,0.64,1) forwards !important; }
3121
+ .osl-tooltip--anim-fade { animation: _osl_tip_fade 0.22s ease-out forwards !important; }
3122
+
3123
+ @keyframes _osl_tip_up { from { opacity:0; transform:translateY(8px); } to { opacity:1; transform:translateY(0); } }
3124
+ @keyframes _osl_tip_down { from { opacity:0; transform:translateY(-8px); } to { opacity:1; transform:translateY(0); } }
3125
+ @keyframes _osl_tip_left { from { opacity:0; transform:translateX(8px); } to { opacity:1; transform:translateX(0); } }
3126
+ @keyframes _osl_tip_right { from { opacity:0; transform:translateX(-8px); } to { opacity:1; transform:translateX(0); } }
3127
+ @keyframes _osl_tip_scale {
3128
+ from { opacity:0; transform:scale(0.72); }
3129
+ to { opacity:1; transform:scale(1); }
3130
+ }
3131
+ @keyframes _osl_tip_bounce {
3132
+ 0% { opacity:0; transform:scale(0.55); }
3133
+ 55% { opacity:1; transform:scale(1.12); }
3134
+ 75% { transform:scale(0.95); }
3135
+ 90% { transform:scale(1.03); }
3136
+ 100% { transform:scale(1); }
3137
+ }
3138
+ @keyframes _osl_tip_fade { from { opacity:0; } to { opacity:1; } }
3139
+ `;
3140
+ class OslTooltipDirective {
3141
+ el;
3142
+ renderer;
3143
+ document;
3144
+ text = '';
3145
+ oslTooltipPosition = 'top';
3146
+ oslTooltipAnimation = 'slide';
3147
+ oslTooltipDisabled = false;
3148
+ oslTooltipMaxWidth = '280px';
3149
+ tooltipEl = null;
3150
+ static cssInjected = false;
3151
+ constructor(el, renderer, document) {
3152
+ this.el = el;
3153
+ this.renderer = renderer;
3154
+ this.document = document;
3155
+ this.injectCss();
3156
+ }
3157
+ show() {
3158
+ if (this.oslTooltipDisabled || !this.text?.trim())
3159
+ return;
3160
+ this.create();
3161
+ this.position();
3162
+ }
3163
+ hide() {
3164
+ this.destroy();
3165
+ }
3166
+ create() {
3167
+ this.destroy();
3168
+ const tip = this.renderer.createElement('div');
3169
+ this.renderer.addClass(tip, 'osl-tooltip');
3170
+ this.renderer.addClass(tip, `osl-tooltip--${this.oslTooltipPosition}`);
3171
+ if (this.oslTooltipAnimation !== 'slide') {
3172
+ this.renderer.addClass(tip, `osl-tooltip--anim-${this.oslTooltipAnimation}`);
3173
+ }
3174
+ this.renderer.setStyle(tip, 'max-width', this.oslTooltipMaxWidth);
3175
+ this.renderer.appendChild(tip, this.renderer.createText(this.text));
3176
+ this.renderer.appendChild(this.document.body, tip);
3177
+ this.tooltipEl = tip;
3178
+ }
3179
+ position() {
3180
+ if (!this.tooltipEl)
3181
+ return;
3182
+ const host = this.el.nativeElement.getBoundingClientRect();
3183
+ const tip = this.tooltipEl.getBoundingClientRect();
3184
+ const gap = 8;
3185
+ let top;
3186
+ let left;
3187
+ switch (this.oslTooltipPosition) {
3188
+ case 'bottom':
3189
+ top = host.bottom + gap;
3190
+ left = host.left + host.width / 2 - tip.width / 2;
3191
+ break;
3192
+ case 'left':
3193
+ top = host.top + host.height / 2 - tip.height / 2;
3194
+ left = host.left - tip.width - gap;
3195
+ break;
3196
+ case 'right':
3197
+ top = host.top + host.height / 2 - tip.height / 2;
3198
+ left = host.right + gap;
3199
+ break;
3200
+ default: // top
3201
+ top = host.top - tip.height - gap;
3202
+ left = host.left + host.width / 2 - tip.width / 2;
3203
+ }
3204
+ left = Math.max(8, Math.min(left, this.document.defaultView.innerWidth - tip.width - 8));
3205
+ top = Math.max(8, top);
3206
+ this.renderer.setStyle(this.tooltipEl, 'top', `${top}px`);
3207
+ this.renderer.setStyle(this.tooltipEl, 'left', `${left}px`);
3208
+ }
3209
+ destroy() {
3210
+ if (this.tooltipEl) {
3211
+ if (this.document.body.contains(this.tooltipEl)) {
3212
+ this.renderer.removeChild(this.document.body, this.tooltipEl);
3213
+ }
3214
+ this.tooltipEl = null;
3215
+ }
3216
+ }
3217
+ injectCss() {
3218
+ if (OslTooltipDirective.cssInjected || this.document.getElementById('_osl_tooltip_css')) {
3219
+ OslTooltipDirective.cssInjected = true;
3220
+ return;
3221
+ }
3222
+ const style = this.document.createElement('style');
3223
+ style.id = '_osl_tooltip_css';
3224
+ style.textContent = TOOLTIP_CSS;
3225
+ this.document.head.appendChild(style);
3226
+ OslTooltipDirective.cssInjected = true;
3227
+ }
3228
+ ngOnDestroy() {
3229
+ this.destroy();
3230
+ }
3231
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: OslTooltipDirective, deps: [{ token: i0.ElementRef }, { token: i0.Renderer2 }, { token: DOCUMENT }], target: i0.ɵɵFactoryTarget.Directive });
3232
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: OslTooltipDirective, isStandalone: true, selector: "[oslTooltip]", inputs: { text: ["oslTooltip", "text"], oslTooltipPosition: "oslTooltipPosition", oslTooltipAnimation: "oslTooltipAnimation", oslTooltipDisabled: "oslTooltipDisabled", oslTooltipMaxWidth: "oslTooltipMaxWidth" }, host: { listeners: { "mouseenter": "show()", "mouseleave": "hide()" } }, ngImport: i0 });
3233
+ }
3234
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: OslTooltipDirective, decorators: [{
3235
+ type: Directive,
3236
+ args: [{ selector: '[oslTooltip]', standalone: true }]
3237
+ }], ctorParameters: () => [{ type: i0.ElementRef }, { type: i0.Renderer2 }, { type: Document, decorators: [{
3238
+ type: Inject,
3239
+ args: [DOCUMENT]
3240
+ }] }], propDecorators: { text: [{
3241
+ type: Input,
3242
+ args: ['oslTooltip']
3243
+ }], oslTooltipPosition: [{
3244
+ type: Input
3245
+ }], oslTooltipAnimation: [{
3246
+ type: Input
3247
+ }], oslTooltipDisabled: [{
3248
+ type: Input
3249
+ }], oslTooltipMaxWidth: [{
3250
+ type: Input
3251
+ }], show: [{
3252
+ type: HostListener,
3253
+ args: ['mouseenter']
3254
+ }], hide: [{
3255
+ type: HostListener,
3256
+ args: ['mouseleave']
3257
+ }] } });
3258
+
3066
3259
  class OslSearchbar {
3067
3260
  label = "Type to Search...";
3068
3261
  onSearch = new EventEmitter();
@@ -3340,6 +3533,7 @@ class OslSetup {
3340
3533
  saveLoading = false;
3341
3534
  restoredRow = null;
3342
3535
  _pendingScrollTop = null;
3536
+ _pendingCardScrollTop = null;
3343
3537
  _isRestoring = false;
3344
3538
  _pendingAutoEditId = null;
3345
3539
  formBodyTpl;
@@ -3347,7 +3541,7 @@ class OslSetup {
3347
3541
  customFooterWrapperTpl;
3348
3542
  searchbar;
3349
3543
  gridRef;
3350
- cardViewport;
3544
+ cardGridRef;
3351
3545
  // ── Inputs ────────────────────────────────────────────────────
3352
3546
  title = '';
3353
3547
  columns = [];
@@ -3373,12 +3567,12 @@ class OslSetup {
3373
3567
  stateKey = '';
3374
3568
  primaryKey = 'id';
3375
3569
  onSave;
3376
- /** Fixed page size used for card view infinite scroll. Defaults to pageSize. */
3570
+ /** Fixed page size used for card view pagination. Defaults to pageSize. */
3377
3571
  cardPageSize;
3378
3572
  /** Optional custom card template. Context: { $implicit: row, index: number } */
3379
3573
  cardTemplate;
3380
- /** Height (px) of each card row used by the virtual scroll engine. Default: 70. */
3381
- cardHeightForVirtualscroll = 70;
3574
+ /** Bootstrap col-* class number for each field in the card body. Default: 3 (4 per row). */
3575
+ cardCol = 3;
3382
3576
  // ── Outputs ───────────────────────────────────────────────────
3383
3577
  onSearch = new EventEmitter();
3384
3578
  onAdd = new EventEmitter();
@@ -3396,14 +3590,10 @@ class OslSetup {
3396
3590
  // ── View mode ─────────────────────────────────────────────────
3397
3591
  viewMode = 'table';
3398
3592
  // ── Card view state ───────────────────────────────────────────
3399
- cardDatasource = [];
3400
- cardPage = 0;
3401
- allCardsLoaded = false;
3593
+ cardCurrentPage = 1;
3594
+ cardPageSizeOptions = [10, 25, 50, 100];
3402
3595
  cardOpenMenuIndex = null;
3403
3596
  cardMenuPosition = { top: 0, left: 0 };
3404
- _cardExpectedPage = 0;
3405
- _cardRestoreTargetPage = 0;
3406
- _needsInitialCardLoad = false;
3407
3597
  onDocumentClick() {
3408
3598
  this.cardOpenMenuIndex = null;
3409
3599
  }
@@ -3425,13 +3615,58 @@ class OslSetup {
3425
3615
  get skeletonCardRows() {
3426
3616
  return Array.from({ length: 6 });
3427
3617
  }
3428
- trackByCard = (_, row) => row[this.primaryKey] ?? _;
3618
+ // ── Card pagination ───────────────────────────────────────────
3619
+ get _cardTotal() {
3620
+ return this.autoMode ? this.datasource.length : this.totalRecords;
3621
+ }
3622
+ get cardTotalPages() {
3623
+ return Math.ceil(this._cardTotal / this._effectiveCardPageSize) || 1;
3624
+ }
3625
+ get cardPagedData() {
3626
+ if (!this.autoMode)
3627
+ return this.datasource;
3628
+ const ps = this._effectiveCardPageSize;
3629
+ return this.datasource.slice((this.cardCurrentPage - 1) * ps, this.cardCurrentPage * ps);
3630
+ }
3631
+ get cardPageNumbers() {
3632
+ const total = this.cardTotalPages;
3633
+ if (total <= 7)
3634
+ return Array.from({ length: total }, (_, i) => i + 1);
3635
+ const pages = [1];
3636
+ if (this.cardCurrentPage > 3)
3637
+ pages.push(-1);
3638
+ for (let i = Math.max(2, this.cardCurrentPage - 1); i <= Math.min(total - 1, this.cardCurrentPage + 1); i++) {
3639
+ pages.push(i);
3640
+ }
3641
+ if (this.cardCurrentPage < total - 2)
3642
+ pages.push(-1);
3643
+ pages.push(total);
3644
+ return pages;
3645
+ }
3646
+ get cardStartRecord() {
3647
+ if (this._cardTotal === 0)
3648
+ return 0;
3649
+ return (this.cardCurrentPage - 1) * this._effectiveCardPageSize + 1;
3650
+ }
3651
+ get cardEndRecord() {
3652
+ return Math.min(this.cardCurrentPage * this._effectiveCardPageSize, this._cardTotal);
3653
+ }
3654
+ cardGoToPage(page) {
3655
+ if (page < 1 || page > this.cardTotalPages)
3656
+ return;
3657
+ this.cardCurrentPage = page;
3658
+ if (!this.autoMode) {
3659
+ this.pageChange.emit({ page, pageSize: this._effectiveCardPageSize, searchValue: this.searchbar?.searchControl?.value ?? '' });
3660
+ }
3661
+ }
3662
+ cardOnPageSizeChange(size) {
3663
+ this.cardCurrentPage = 1;
3664
+ this.cardPageSize = size;
3665
+ this.pageSizeChange.emit({ page: 1, pageSize: Number(size), searchValue: this.searchbar?.searchControl?.value ?? '' });
3666
+ }
3429
3667
  // ── Lifecycle ─────────────────────────────────────────────────
3430
3668
  ngOnInit() {
3431
3669
  this._loadViewMode();
3432
- if (this.viewMode === 'card') {
3433
- this._needsInitialCardLoad = true;
3434
- }
3435
3670
  const route = this._injector.get(ActivatedRoute, null);
3436
3671
  if (route) {
3437
3672
  const id = route.snapshot.queryParamMap.get('id');
@@ -3441,10 +3676,6 @@ class OslSetup {
3441
3676
  }
3442
3677
  ngAfterViewInit() {
3443
3678
  this.statemainTain();
3444
- if (this._needsInitialCardLoad) {
3445
- this._needsInitialCardLoad = false;
3446
- setTimeout(() => { this._startCardLoad(); });
3447
- }
3448
3679
  if (this._pendingAutoEditId !== null) {
3449
3680
  const id = this._pendingAutoEditId;
3450
3681
  this._pendingAutoEditId = null;
@@ -3458,70 +3689,22 @@ class OslSetup {
3458
3689
  }
3459
3690
  ngOnChanges(changes) {
3460
3691
  if (changes['datasource']) {
3461
- if (this.viewMode === 'card') {
3462
- if (this.autoMode) {
3463
- // Local pagination: datasource changed (search filtered or initial load), reset
3464
- if (!changes['datasource'].firstChange) {
3465
- if (this._cardRestoreTargetPage > 0) {
3466
- const targetPage = this._cardRestoreTargetPage;
3467
- this._cardRestoreTargetPage = 0;
3468
- const targetCount = targetPage * this._effectiveCardPageSize;
3469
- this.cardDatasource = this.datasource.slice(0, targetCount);
3470
- this.cardPage = targetPage;
3471
- this.allCardsLoaded = this.cardDatasource.length >= this.datasource.length;
3472
- if (this._pendingScrollTop !== null) {
3473
- const top = this._pendingScrollTop;
3474
- this._pendingScrollTop = null;
3475
- setTimeout(() => { this.cardViewport?.scrollToOffset(top); }, 50);
3476
- }
3477
- }
3478
- else {
3479
- this._loadCardPageLocally(1);
3480
- }
3481
- }
3482
- }
3483
- else if (this._cardExpectedPage !== 0) {
3484
- const newData = changes['datasource'].currentValue ?? [];
3485
- const isFirstPage = this._cardExpectedPage === 1;
3486
- this._cardExpectedPage = 0;
3487
- if (isFirstPage) {
3488
- this.cardDatasource = [...newData];
3489
- this.cardPage = 1;
3490
- this.allCardsLoaded = false;
3491
- }
3492
- else if (newData.length > 0) {
3493
- this.cardDatasource = [...this.cardDatasource, ...newData];
3494
- this.cardPage++;
3495
- }
3496
- if (newData.length === 0 || (this.totalRecords > 0 && this.cardDatasource.length >= this.totalRecords)) {
3497
- this.allCardsLoaded = true;
3498
- }
3499
- // Multi-page state restore: continue loading until target page is reached
3500
- if (this._cardRestoreTargetPage > 0 && this.cardPage < this._cardRestoreTargetPage && !this.allCardsLoaded) {
3501
- const nextPage = this.cardPage + 1;
3502
- this._cardExpectedPage = nextPage;
3503
- this.pageChange.emit({ page: nextPage, pageSize: this._effectiveCardPageSize, searchValue: this.searchbar?.searchControl?.value ?? '' });
3504
- }
3505
- else if (this._cardRestoreTargetPage > 0 && (this.cardPage >= this._cardRestoreTargetPage || this.allCardsLoaded)) {
3506
- this._cardRestoreTargetPage = 0;
3507
- if (this._pendingScrollTop !== null) {
3508
- const top = this._pendingScrollTop;
3509
- this._pendingScrollTop = null;
3510
- setTimeout(() => { this.cardViewport?.scrollToOffset(top); }, 50);
3692
+ if (this.viewMode === 'card' && this.autoMode && !changes['datasource'].firstChange) {
3693
+ this.cardCurrentPage = 1;
3694
+ }
3695
+ else if (this.viewMode === 'card' && !this.autoMode && this._pendingCardScrollTop !== null) {
3696
+ const ds = changes['datasource'].currentValue;
3697
+ if (ds?.length > 0) {
3698
+ const top = this._pendingCardScrollTop;
3699
+ this._pendingCardScrollTop = null;
3700
+ setTimeout(() => {
3701
+ if (this.cardGridRef?.nativeElement) {
3702
+ this.cardGridRef.nativeElement.scrollTop = top;
3511
3703
  }
3512
- }
3513
- }
3514
- else if (!changes['datasource'].firstChange) {
3515
- // External refresh (e.g. after approve/delete via more-actions) — replace cards with fresh data
3516
- const newData = changes['datasource'].currentValue ?? [];
3517
- this.cardDatasource = [...newData];
3518
- this.cardPage = 1;
3519
- this.allCardsLoaded = newData.length === 0
3520
- || (this.totalRecords > 0 && newData.length >= this.totalRecords)
3521
- || newData.length < this._effectiveCardPageSize;
3704
+ }, 50);
3522
3705
  }
3523
3706
  }
3524
- else if (this._pendingScrollTop !== null) {
3707
+ else if (this.viewMode === 'table' && this._pendingScrollTop !== null) {
3525
3708
  const ds = changes['datasource'].currentValue;
3526
3709
  if (ds?.length > 0) {
3527
3710
  const top = this._pendingScrollTop;
@@ -3552,78 +3735,18 @@ class OslSetup {
3552
3735
  if (key)
3553
3736
  localStorage.setItem(key, mode);
3554
3737
  if (mode === 'card') {
3555
- this._startCardLoad();
3738
+ this.cardCurrentPage = 1;
3739
+ if (!this.autoMode) {
3740
+ this.pageChange.emit({ page: 1, pageSize: this._effectiveCardPageSize, searchValue: this.searchbar?.searchControl?.value ?? '' });
3741
+ }
3556
3742
  }
3557
3743
  else {
3558
- this._cardExpectedPage = 0;
3559
3744
  if (this.gridRef)
3560
3745
  this.gridRef.currentPage = 1;
3561
3746
  this.pageChange.emit({ page: 1, pageSize: this.pageSize, searchValue: this.searchbar?.searchControl?.value ?? '' });
3562
3747
  }
3563
3748
  }
3564
- // ── Card view helpers ─────────────────────────────────────────
3565
- _startCardLoad() {
3566
- this.cardDatasource = [];
3567
- this.cardPage = 0;
3568
- this.allCardsLoaded = false;
3569
- this._cardRestoreTargetPage = 0;
3570
- if (this.autoMode) {
3571
- this._loadCardPageLocally(1);
3572
- }
3573
- else {
3574
- this._cardExpectedPage = 1;
3575
- this.pageChange.emit({ page: 1, pageSize: this._effectiveCardPageSize, searchValue: this.searchbar?.searchControl?.value ?? '' });
3576
- }
3577
- }
3578
- _loadCardPageLocally(page) {
3579
- const ps = this._effectiveCardPageSize;
3580
- const slice = this.datasource.slice((page - 1) * ps, page * ps);
3581
- if (page === 1) {
3582
- this.cardDatasource = [...slice];
3583
- this.allCardsLoaded = false;
3584
- }
3585
- else {
3586
- this.cardDatasource = [...this.cardDatasource, ...slice];
3587
- }
3588
- this.cardPage = page;
3589
- if (this.datasource.length > 0 && (this.cardDatasource.length >= this.datasource.length || slice.length < ps)) {
3590
- this.allCardsLoaded = true;
3591
- }
3592
- }
3593
- loadMoreCards() {
3594
- if (this.loading || this.allCardsLoaded)
3595
- return;
3596
- if (this.autoMode) {
3597
- if (this.cardDatasource.length >= this.datasource.length) {
3598
- this.allCardsLoaded = true;
3599
- return;
3600
- }
3601
- this._loadCardPageLocally(this.cardPage + 1);
3602
- return;
3603
- }
3604
- if (this._cardExpectedPage !== 0)
3605
- return;
3606
- if (this.totalRecords > 0 && this.cardDatasource.length >= this.totalRecords) {
3607
- this.allCardsLoaded = true;
3608
- return;
3609
- }
3610
- const nextPage = this.cardPage + 1;
3611
- this._cardExpectedPage = nextPage;
3612
- this.pageChange.emit({ page: nextPage, pageSize: this._effectiveCardPageSize, searchValue: this.searchbar?.searchControl?.value ?? '' });
3613
- }
3614
- onVirtualScrollIndexChange(firstIndex) {
3615
- const viewportSize = this.cardViewport?.getViewportSize() ?? 0;
3616
- const visibleCount = viewportSize > 0 ? Math.ceil(viewportSize / this.cardHeightForVirtualscroll) : 10;
3617
- if (firstIndex + visibleCount + 3 >= this.cardDatasource.length) {
3618
- this.loadMoreCards();
3619
- }
3620
- }
3621
- isHighlightedCard(row) {
3622
- if (!this.restoredRow || !this.primaryKey)
3623
- return false;
3624
- const val = row[this.primaryKey];
3625
- return val !== undefined && val === this.restoredRow[this.primaryKey];
3626
- }
3749
+ // ── Card helpers ──────────────────────────────────────────────
3627
3750
  toggleCardMenu(index, event) {
3628
3751
  event.stopPropagation();
3629
3752
  if (this.cardOpenMenuIndex === index) {
@@ -3636,6 +3759,12 @@ class OslSetup {
3636
3759
  this.cardMenuPosition = { top: rect.bottom + 6, left };
3637
3760
  this.cardOpenMenuIndex = index;
3638
3761
  }
3762
+ isHighlightedCard(row) {
3763
+ if (!this.restoredRow || !this.primaryKey)
3764
+ return false;
3765
+ const val = row[this.primaryKey];
3766
+ return val !== undefined && val === this.restoredRow[this.primaryKey];
3767
+ }
3639
3768
  getCellValue(row, col) {
3640
3769
  const raw = row[col.key];
3641
3770
  if (col.displayFn)
@@ -3662,49 +3791,31 @@ class OslSetup {
3662
3791
  const state = this._stateService.consume(this.stateKey);
3663
3792
  if (!state)
3664
3793
  return;
3665
- this._pendingScrollTop = state.scrollTop;
3666
3794
  this.restoredRow = state.highlightedRow;
3667
3795
  if (this.viewMode === 'card') {
3668
- this._needsInitialCardLoad = false;
3669
- this.cardDatasource = [];
3670
- this.cardPage = 0;
3671
- this.allCardsLoaded = false;
3672
- if (this.autoMode) {
3673
- if (this.datasource.length > 0) {
3674
- // Data already available — restore immediately
3675
- const targetCount = state.page * this._effectiveCardPageSize;
3676
- this.cardDatasource = this.datasource.slice(0, targetCount);
3677
- this.cardPage = state.page;
3678
- this.allCardsLoaded = this.cardDatasource.length >= this.datasource.length;
3679
- if (this._pendingScrollTop !== null) {
3680
- const top = this._pendingScrollTop;
3681
- this._pendingScrollTop = null;
3682
- setTimeout(() => { this.cardViewport?.scrollToOffset(top); }, 50);
3683
- }
3684
- }
3685
- else {
3686
- // Datasource not yet populated — defer restore to ngOnChanges
3687
- this._cardRestoreTargetPage = state.page;
3688
- }
3689
- this.onStateRestored.emit(state);
3796
+ this.cardCurrentPage = state.page;
3797
+ if (this.searchbar && state.searchValue) {
3798
+ this._isRestoring = true;
3799
+ this.searchbar.searchControl.setValue(state.searchValue, { emitEvent: false });
3690
3800
  }
3691
- else {
3692
- this._cardRestoreTargetPage = state.page;
3693
- if (this.searchbar && state.searchValue) {
3694
- this._isRestoring = true;
3695
- this.searchbar.searchControl.setValue(state.searchValue, { emitEvent: false });
3696
- }
3697
- this._cardExpectedPage = 1;
3698
- if (state.searchValue) {
3699
- this.onSearchSetup(state.searchValue);
3700
- }
3701
- else {
3702
- this.pageChange.emit({ page: 1, pageSize: this._effectiveCardPageSize, searchValue: '' });
3703
- }
3704
- this.onStateRestored.emit(state);
3801
+ if (state.searchValue) {
3802
+ this.onSearchSetup(state.searchValue);
3803
+ }
3804
+ else if (!this.autoMode) {
3805
+ this._pendingCardScrollTop = state.scrollTop;
3806
+ this.pageChange.emit({ page: state.page, pageSize: state.pageSize, searchValue: '' });
3705
3807
  }
3808
+ else if (state.scrollTop > 0) {
3809
+ setTimeout(() => {
3810
+ if (this.cardGridRef?.nativeElement) {
3811
+ this.cardGridRef.nativeElement.scrollTop = state.scrollTop;
3812
+ }
3813
+ }, 50);
3814
+ }
3815
+ this.onStateRestored.emit(state);
3706
3816
  }
3707
3817
  else {
3818
+ this._pendingScrollTop = state.scrollTop;
3708
3819
  if (this.gridRef) {
3709
3820
  this.gridRef.currentPage = state.page;
3710
3821
  this.gridRef.pageSize = state.pageSize;
@@ -3730,18 +3841,12 @@ class OslSetup {
3730
3841
  // ── Search ────────────────────────────────────────────────────
3731
3842
  onSearchSetup(event) {
3732
3843
  if (this.viewMode === 'card' && this.autoMode) {
3733
- // Local mode: parent filters datasource; ngOnChanges will repopulate cards
3844
+ this.cardCurrentPage = 1;
3734
3845
  this.onSearch.emit(event);
3735
3846
  return;
3736
3847
  }
3737
- if (this.viewMode === 'card') {
3738
- this.cardDatasource = [];
3739
- this.cardPage = 0;
3740
- this.allCardsLoaded = false;
3741
- this._cardExpectedPage = 1;
3742
- }
3743
3848
  if (this._isRestoring) {
3744
- const restoredPage = this.viewMode === 'card' ? 1 : (this.gridRef?.currentPage ?? 1);
3849
+ const restoredPage = this.viewMode === 'card' ? this.cardCurrentPage : (this.gridRef?.currentPage ?? 1);
3745
3850
  this._isRestoring = false;
3746
3851
  this.pageChange.emit({
3747
3852
  page: restoredPage,
@@ -3751,6 +3856,9 @@ class OslSetup {
3751
3856
  this.onSearch.emit(event);
3752
3857
  return;
3753
3858
  }
3859
+ if (this.viewMode === 'card') {
3860
+ this.cardCurrentPage = 1;
3861
+ }
3754
3862
  if (this.gridRef) {
3755
3863
  this.gridRef.clearRestorePage();
3756
3864
  this.gridRef.currentPage = 1;
@@ -3760,7 +3868,7 @@ class OslSetup {
3760
3868
  pageSize: this.viewMode === 'card' ? this._effectiveCardPageSize : (this.gridRef?.pageSize || 10),
3761
3869
  searchValue: event,
3762
3870
  sortASC: this.gridRef?.sortAsc,
3763
- sortKey: this.gridRef?.sortKey
3871
+ sortKey: this.gridRef?.sortKey,
3764
3872
  });
3765
3873
  this.onSearch.emit(event);
3766
3874
  }
@@ -3778,7 +3886,7 @@ class OslSetup {
3778
3886
  eventEmitter.emit({ ...event, ...{
3779
3887
  searchValue: this.searchbar?.searchControl?.value,
3780
3888
  sortKey: this.gridRef?.sortKey,
3781
- sortASC: this.gridRef?.sortAsc
3889
+ sortASC: this.gridRef?.sortAsc,
3782
3890
  } });
3783
3891
  }
3784
3892
  // ── Dialog actions ────────────────────────────────────────────
@@ -3801,12 +3909,10 @@ class OslSetup {
3801
3909
  this.dialogMode = 'edit';
3802
3910
  if (this.stateKey) {
3803
3911
  this._stateService.save(this.stateKey, {
3804
- page: this.viewMode === 'card' ? this.cardPage : (this.gridRef?.currentPage ?? 1),
3912
+ page: this.viewMode === 'card' ? this.cardCurrentPage : (this.gridRef?.currentPage ?? 1),
3805
3913
  pageSize: this.viewMode === 'card' ? this._effectiveCardPageSize : (this.gridRef?.pageSize ?? this.pageSize),
3806
3914
  searchValue: this.searchbar?.searchControl?.value ?? '',
3807
- scrollTop: this.viewMode === 'card'
3808
- ? (this.cardViewport?.measureScrollOffset('top') ?? 0)
3809
- : (this.gridRef?.getScrollTop() ?? 0),
3915
+ scrollTop: this.viewMode === 'card' ? (this.cardGridRef?.nativeElement?.scrollTop ?? 0) : (this.gridRef?.getScrollTop() ?? 0),
3810
3916
  highlightedRow: row,
3811
3917
  });
3812
3918
  }
@@ -3873,11 +3979,11 @@ class OslSetup {
3873
3979
  });
3874
3980
  }
3875
3981
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: OslSetup, deps: [], target: i0.ɵɵFactoryTarget.Component });
3876
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: OslSetup, isStandalone: false, selector: "osl-setup", inputs: { title: "title", columns: "columns", datasource: "datasource", isPaginated: "isPaginated", pageSize: "pageSize", autoMode: "autoMode", tableHeight: "tableHeight", totalRecords: "totalRecords", loading: "loading", dialogWidth: "dialogWidth", formElements: "formElements", beforeDisplay: "beforeDisplay", onAddEditFn: "onAddEditFn", isLister: "isLister", canAdd: "canAdd", canEdit: "canEdit", canDelete: "canDelete", moreMenuActions: "moreMenuActions", customFormFooter: "customFormFooter", customHeaderTemp: "customHeaderTemp", partialCustomHeaderTemp: "partialCustomHeaderTemp", stateKey: "stateKey", primaryKey: "primaryKey", onSave: "onSave", cardPageSize: "cardPageSize", cardTemplate: "cardTemplate", cardHeightForVirtualscroll: "cardHeightForVirtualscroll" }, outputs: { onSearch: "onSearch", onAdd: "onAdd", onEdit: "onEdit", onDelete: "onDelete", pageChange: "pageChange", pageSizeChange: "pageSizeChange", sortChange: "sortChange", onRowClick: "onRowClick", onStateRestored: "onStateRestored" }, host: { listeners: { "document:click": "onDocumentClick()" } }, viewQueries: [{ propertyName: "formBodyTpl", first: true, predicate: ["formBodyTpl"], descendants: true }, { propertyName: "formFooterTpl", first: true, predicate: ["formFooterTpl"], descendants: true }, { propertyName: "customFooterWrapperTpl", first: true, predicate: ["customFooterWrapperTpl"], descendants: true }, { propertyName: "searchbar", first: true, predicate: ["searchbar"], descendants: true }, { propertyName: "gridRef", first: true, predicate: ["gridRef"], descendants: true }, { propertyName: "cardViewport", first: true, predicate: ["cardViewport"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"p-2\">\n\n <!-- \u2500\u2500 Header \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div class=\"osl-setup-header\">\n <h5 class=\"mb-0\">{{ title }}</h5>\n <div class=\"d-flex align-items-center gap-2\">\n <osl-searchbar #searchbar class=\"mx-2\" (onSearch)=\"onSearchSetup($event)\"></osl-searchbar>\n\n <!-- View Toggle -->\n @if(!isLister){\n <div class=\"osl-view-toggle\">\n <button class=\"osl-view-toggle-btn\" [class.osl-view-toggle-btn--active]=\"viewMode === 'table'\"\n (click)=\"toggleView('table')\" title=\"Table view\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"2\"/>\n <line x1=\"3\" y1=\"9\" x2=\"21\" y2=\"9\"/>\n <line x1=\"3\" y1=\"15\" x2=\"21\" y2=\"15\"/>\n <line x1=\"9\" y1=\"9\" x2=\"9\" y2=\"21\"/>\n </svg>\n </button>\n <button class=\"osl-view-toggle-btn\" [class.osl-view-toggle-btn--active]=\"viewMode === 'card'\"\n (click)=\"toggleView('card')\" title=\"Card view\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <rect x=\"3\" y=\"3\" width=\"7\" height=\"7\" rx=\"1.5\"/>\n <rect x=\"14\" y=\"3\" width=\"7\" height=\"7\" rx=\"1.5\"/>\n <rect x=\"3\" y=\"14\" width=\"7\" height=\"7\" rx=\"1.5\"/>\n <rect x=\"14\" y=\"14\" width=\"7\" height=\"7\" rx=\"1.5\"/>\n </svg>\n </button>\n </div>\n }\n\n @if (!isLister && canAdd) {\n <osl-button variant=\"secondary\" size=\"sm\" [label]=\"'Add ' + title\" (clickEv)=\"openAddDialog()\"></osl-button>\n }\n </div>\n </div>\n\n <!-- \u2500\u2500 Table View \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (viewMode === 'table') {\n <div class=\"osl-setup-body my-2\">\n <osl-grid\n #gridRef\n [columns]=\"columnsWithActions\"\n [(datasource)]=\"datasource\"\n [isPaginated]=\"isPaginated\"\n [pageSize]=\"pageSize\"\n [autoMode]=\"autoMode\"\n [tableHeight]=\"tableHeight\"\n [totalRecords]=\"totalRecords\"\n [loading]=\"loading\"\n [moreMenuActions]=\"moreMenuActions\"\n [canEdit]=\"canEdit\"\n [canDelete]=\"canDelete\"\n [highlightedRow]=\"restoredRow\"\n [primaryKey]=\"primaryKey\"\n (editClick)=\"openEditDialog($event)\"\n (deleteClick)=\"onDeleteClick($event)\"\n (pageChange)=\"onPageChange(pageChange, $event)\"\n (pageSizeChange)=\"onPageChange(pageSizeChange, $event)\"\n (sortChange)=\"sortChange.emit($event)\"\n [isSelectable]=\"isLister\"\n (onRowClick)=\"onRowClick.emit($event)\"\n />\n </div>\n }\n\n <!-- \u2500\u2500 Card View \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (viewMode === 'card') {\n\n <!-- Skeleton on initial load -->\n @if (loading && cardDatasource.length === 0) {\n <div class=\"osl-card-container my-2\" [style.height]=\"tableHeight\">\n <div class=\"osl-card-grid\">\n @for (sk of skeletonCardRows; track $index) {\n <div class=\"osl-card osl-card--skeleton\" [style.height.px]=\"cardHeightForVirtualscroll\">\n <div class=\"osl-card-inner\">\n <div class=\"osl-card-header\">\n <div class=\"osl-card-avatar osl-card-avatar--skeleton\"></div>\n <div style=\"flex:1;min-width:0\">\n <div class=\"osl-sk-line osl-sk-line--title\"></div>\n </div>\n </div>\n <div class=\"osl-card-divider\"></div>\n <div class=\"osl-card-body\">\n <div class=\"osl-card-field\"><div class=\"osl-sk-line osl-sk-line--label\"></div><div class=\"osl-sk-line\"></div></div>\n <div class=\"osl-card-field\"><div class=\"osl-sk-line osl-sk-line--label\"></div><div class=\"osl-sk-line osl-sk-line--short\"></div></div>\n <div class=\"osl-card-field\"><div class=\"osl-sk-line osl-sk-line--label\"></div><div class=\"osl-sk-line\"></div></div>\n </div>\n </div>\n </div>\n }\n </div>\n </div>\n }\n\n <!-- Empty state -->\n @else if (cardDatasource.length === 0 && !loading) {\n <div class=\"osl-card-container my-2\" [style.height]=\"tableHeight\">\n <div class=\"osl-card-empty\">\n <svg class=\"osl-card-empty-svg\" viewBox=\"0 0 180 160\" fill=\"none\">\n <ellipse cx=\"90\" cy=\"140\" rx=\"60\" ry=\"8\" fill=\"#f3f4f6\"/>\n <rect x=\"30\" y=\"30\" width=\"120\" height=\"96\" rx=\"10\" fill=\"#f9fafb\" stroke=\"#e5e7eb\" stroke-width=\"2\"/>\n <rect x=\"44\" y=\"48\" width=\"92\" height=\"10\" rx=\"5\" fill=\"#e5e7eb\"/>\n <rect x=\"44\" y=\"66\" width=\"72\" height=\"8\" rx=\"4\" fill=\"#f3f4f6\"/>\n <rect x=\"44\" y=\"82\" width=\"84\" height=\"8\" rx=\"4\" fill=\"#f3f4f6\"/>\n <rect x=\"44\" y=\"98\" width=\"56\" height=\"8\" rx=\"4\" fill=\"#f3f4f6\"/>\n <circle cx=\"140\" cy=\"108\" r=\"26\" fill=\"#fff\" stroke=\"#e5e7eb\" stroke-width=\"2\"/>\n <line x1=\"133\" y1=\"101\" x2=\"147\" y2=\"115\" stroke=\"#d1d5db\" stroke-width=\"2.5\" stroke-linecap=\"round\"/>\n <line x1=\"147\" y1=\"101\" x2=\"133\" y2=\"115\" stroke=\"#d1d5db\" stroke-width=\"2.5\" stroke-linecap=\"round\"/>\n </svg>\n <p class=\"osl-card-empty-title\">No records found</p>\n <p class=\"osl-card-empty-desc\">Try adjusting your search or filter criteria</p>\n </div>\n </div>\n }\n\n <!-- Virtual scroll card list -->\n @else {\n <div class=\"osl-card-vs-wrapper my-2\" [style.height]=\"tableHeight\">\n <cdk-virtual-scroll-viewport\n #cardViewport\n [itemSize]=\"cardHeightForVirtualscroll\"\n class=\"osl-card-virtual-viewport\"\n (scrolledIndexChange)=\"onVirtualScrollIndexChange($event)\">\n\n <div *cdkVirtualFor=\"let row of cardDatasource; trackBy: trackByCard; let i = index\"\n class=\"osl-card-vs-row\"\n [style.height.px]=\"cardHeightForVirtualscroll\">\n\n @if (cardTemplate) {\n <ng-container *ngTemplateOutlet=\"cardTemplate; context: { $implicit: row, index: i }\"></ng-container>\n } @else {\n <div class=\"osl-card\"\n [class.osl-card--highlighted]=\"isHighlightedCard(row)\"\n [class.osl-card--selectable]=\"isLister\"\n (click)=\"isLister ? onRowClick.emit(row) : null\">\n\n <div class=\"osl-card-inner\">\n\n <!-- Left: avatar + title -->\n <div class=\"osl-card-header\">\n <!-- @if (cardTitleColumn) {\n <div class=\"osl-card-avatar\">{{ getCardInitial(row) }}</div>\n } -->\n <div class=\"osl-card-title-wrap\">\n <span class=\"osl-card-title\">\n @if (cardTitleColumn) {\n @switch (cardTitleColumn.displayType) {\n @case ('date') { {{ row[cardTitleColumn.key] | date }} }\n @case ('datetime') { {{ row[cardTitleColumn.key] | date:'medium' }} }\n @case ('time') { {{ row[cardTitleColumn.key] | date:'shortTime' }} }\n @case ('customDateFormat') { {{ row[cardTitleColumn.key] | date:cardTitleColumn.customDateFormat }} }\n @default { {{ getCellValue(row, cardTitleColumn) }} }\n }\n }\n </span>\n </div>\n </div>\n\n <!-- Vertical divider -->\n <div class=\"osl-card-divider\"></div>\n\n <!-- Middle: body fields (horizontal) -->\n <div class=\"osl-card-body\">\n @for (col of cardBodyColumns; track col.key) {\n <div class=\"osl-card-field\">\n <span class=\"osl-card-label\">{{ col.label }}</span>\n <span class=\"osl-card-value\" [class.link]=\"col.displayType === 'link'\" (click)=\"col.click ? col.click(row, col) : null\">\n @switch (col.displayType) {\n @case ('date') { {{ row[col.key] | date }} }\n @case ('datetime') { {{ row[col.key] | date:'medium' }} }\n @case ('time') { {{ row[col.key] | date:'shortTime' }} }\n @case ('customDateFormat') { {{ row[col.key] | date:col.customDateFormat }} }\n @default { {{ getCellValue(row, col) }} }\n }\n </span>\n </div>\n }\n </div>\n\n <!-- Right: action buttons -->\n @if (!isLister && ((hasForm && (canEdit || canDelete)) || (moreMenuActions.length > 0 && hasVisibleActions(row)))) {\n <div class=\"osl-card-actions\">\n \n \n @if (hasForm && canDelete) {\n <button class=\"osl-card-action-btn osl-card-action-btn--delete\"\n (click)=\"$event.stopPropagation(); onDeleteClick(row)\" title=\"Delete\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polyline points=\"3 6 5 6 21 6\"/>\n <path d=\"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2\"/>\n </svg>\n </button>\n }\n </div>\n }\n @if (hasForm && canEdit) {\n <button class=\"osl-card-action-btn osl-card-action-btn--edit\"\n (click)=\"$event.stopPropagation(); openEditDialog(row)\" title=\"Edit\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7\"/>\n <path d=\"M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z\"/>\n </svg>\n </button>\n }\n\n @if (moreMenuActions.length > 0 && hasVisibleActions(row)) {\n <button class=\"osl-card-action-btn osl-card-action-btn--more\"\n (click)=\"toggleCardMenu(i, $event)\" title=\"More actions\">\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <circle cx=\"12\" cy=\"5\" r=\"2.2\"/>\n <circle cx=\"12\" cy=\"12\" r=\"2.2\"/>\n <circle cx=\"12\" cy=\"19\" r=\"2.2\"/>\n </svg>\n </button>\n }\n\n </div>\n </div>\n }\n </div>\n </cdk-virtual-scroll-viewport>\n </div>\n\n <!-- Infinite scroll loader (outside viewport) -->\n @if (loading) {\n <div class=\"osl-card-loader\">\n <div class=\"osl-card-loader-track\">\n <div class=\"osl-card-loader-bar\"></div>\n </div>\n <span class=\"osl-card-loader-text\">Loading more\u2026</span>\n </div>\n }\n\n <!-- All loaded footer (outside viewport) -->\n @if (allCardsLoaded && !loading && cardDatasource.length > 0) {\n <div class=\"osl-card-all-loaded\">\n <span class=\"osl-card-all-loaded-line\"></span>\n <span class=\"osl-card-all-loaded-text\">All {{ totalRecords || cardDatasource.length }} records loaded</span>\n <span class=\"osl-card-all-loaded-line\"></span>\n </div>\n }\n }\n }\n\n</div>\n\n<!-- Dialog: Form Body -->\n<ng-template #formBodyTpl>\n <osl-dynamic-form\n [skeletonLoading]=\"formLoading\"\n [elements]=\"formElements\"\n [(model)]=\"dialogModel\"\n ></osl-dynamic-form>\n</ng-template>\n\n<!-- Dialog: Form Footer -->\n<ng-template #formFooterTpl>\n <div class=\"osl-setup-dialog-footer\">\n <osl-button [loading]=\"saveLoading\" variant=\"secondary\" label=\"Save\" (click)=\"saveDialog()\"></osl-button>\n </div>\n</ng-template>\n\n<ng-template #customFooterWrapperTpl let-data>\n <ng-container *ngTemplateOutlet=\"customFormFooter!; context: { $implicit: { dialogModel: dialogModel, dialogMode: dialogMode, dialogRef: data.dialogRef } }\"></ng-container>\n</ng-template>\n\n<!-- Floating card more-actions menu \u2014 rendered outside card DOM to avoid transform containment -->\n@if (viewMode === 'card' && cardOpenMenuIndex !== null && moreMenuActions.length > 0) {\n <div class=\"osl-card-menu\"\n [style.top.px]=\"cardMenuPosition.top\"\n [style.left.px]=\"cardMenuPosition.left\"\n (click)=\"$event.stopPropagation()\">\n <div class=\"osl-card-menu-header\">Actions</div>\n @for (action of moreMenuActions; track $index) {\n @if (!action.hideIf || !action.hideIf(cardDatasource[cardOpenMenuIndex])) {\n <button class=\"osl-card-menu-item\"\n (click)=\"action.click(cardDatasource[cardOpenMenuIndex]); cardOpenMenuIndex = null\">\n <span class=\"osl-card-menu-dot\"></span>\n {{ action.labelIf ? action.labelIf(cardDatasource[cardOpenMenuIndex]) : action.label }}\n </button>\n }\n }\n </div>\n}\n", styles: [".osl-setup-header{display:flex;align-items:center;justify-content:space-between}.osl-setup-dialog-footer{display:flex;align-items:center;justify-content:flex-end;gap:10px;width:100%}.dialog-cancel-btn{display:flex;align-items:center;gap:6px;height:38px;font-size:13px;font-weight:500;border-color:var(--osl-border-color, #d1d5db);color:var(--osl-secondary, #6b7280);border-radius:var(--osl-border-radius, 4px);padding:0 16px;transition:all .2s ease}.dialog-cancel-btn mat-icon{font-size:17px;width:17px;height:17px}.dialog-cancel-btn:hover{border-color:#9ca3af;background:#f9fafb;color:#374151}.dialog-save-btn{display:flex;align-items:center;gap:6px;height:38px;font-size:13px;font-weight:500;background:linear-gradient(135deg,var(--osl-primary, #2563eb),#3b82f6);color:#fff;border-radius:var(--osl-border-radius, 4px);padding:0 18px;transition:all .2s ease;box-shadow:0 2px 8px #2563eb40}.dialog-save-btn mat-icon{font-size:17px;width:17px;height:17px}.dialog-save-btn:hover{background:linear-gradient(135deg,var(--osl-primary-hover, #1d4ed8),#2563eb);box-shadow:0 4px 14px #2563eb66;transform:translateY(-1px)}.dialog-save-btn:active{transform:translateY(0);box-shadow:0 2px 8px #2563eb40}.osl-view-toggle{display:flex;border:1.5px solid var(--osl-border-color, #e5e7eb);border-radius:8px;overflow:hidden;flex-shrink:0;background:#fff}.osl-view-toggle-btn{display:inline-flex;align-items:center;justify-content:center;width:34px;height:34px;border:none;background:transparent;cursor:pointer;color:#9ca3af;transition:background .15s,color .15s;padding:0}.osl-view-toggle-btn svg{width:16px;height:16px;transition:stroke .15s}.osl-view-toggle-btn:first-child{border-right:1.5px solid var(--osl-border-color, #e5e7eb)}.osl-view-toggle-btn--active{background:var(--osl-primary, #6366f1);color:#fff}.osl-view-toggle-btn--active svg{stroke:#fff}.osl-view-toggle-btn:not(.osl-view-toggle-btn--active):hover{background:#f3f4f6;color:#374151}.osl-card-container{overflow-y:auto;overflow-x:hidden;border-radius:12px;padding:4px 2px 16px;box-sizing:border-box}.osl-card-vs-wrapper{display:flex;flex-direction:column;overflow:hidden;box-sizing:border-box}.osl-card-virtual-viewport{flex:1;min-height:0;width:100%}.osl-card-vs-row{padding:0 2px 4px;box-sizing:border-box}.osl-card-vs-row>.osl-card{height:100%}.osl-card-grid{display:flex;flex-direction:column;gap:10px;padding:4px}@keyframes osl-card-enter{0%{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}.osl-card-grid .osl-card:nth-child(1){animation-delay:0s}.osl-card-grid .osl-card:nth-child(2){animation-delay:.03s}.osl-card-grid .osl-card:nth-child(3){animation-delay:.06s}.osl-card-grid .osl-card:nth-child(4){animation-delay:.09s}.osl-card-grid .osl-card:nth-child(5){animation-delay:.12s}.osl-card-grid .osl-card:nth-child(6){animation-delay:.15s}.osl-card-grid .osl-card:nth-child(7){animation-delay:.18s}.osl-card-grid .osl-card:nth-child(8){animation-delay:.21s}.osl-card{position:relative;background:#fff;border-radius:14px;border:1px solid #eaecf0;box-shadow:0 1px 4px #1018280f,0 1px 2px #1018280a;overflow:hidden;transition:transform .22s cubic-bezier(.34,1.56,.64,1),box-shadow .22s ease;animation:osl-card-enter .28s ease both}.osl-card:nth-child(6n+1){--card-accent: #6366f1;--card-accent-bg: #eef2ff}.osl-card:nth-child(6n+2){--card-accent: #0ea5e9;--card-accent-bg: #f0f9ff}.osl-card:nth-child(6n+3){--card-accent: #10b981;--card-accent-bg: #ecfdf5}.osl-card:nth-child(6n+4){--card-accent: #f59e0b;--card-accent-bg: #fffbeb}.osl-card:nth-child(6n+5){--card-accent: #f43f5e;--card-accent-bg: #fff1f2}.osl-card:nth-child(6n){--card-accent: #8b5cf6;--card-accent-bg: #faf5ff}.osl-card:before{content:\"\";position:absolute;top:0;left:0;bottom:0;width:4px;background:var(--card-accent, var(--osl-primary, #6366f1))}.osl-card:hover{transform:translateY(-2px);box-shadow:0 6px 20px #10182817,0 2px 8px #1018280d;border-color:#d0d5dd}.osl-card--highlighted{outline:2px solid var(--osl-primary, #6366f1);outline-offset:2px;animation:osl-card-enter .28s ease both,osl-card-highlight-pulse 2.5s ease-out .3s both}.osl-card--selectable{cursor:pointer}.osl-card--skeleton{pointer-events:none;animation:osl-card-enter .28s ease both}.osl-card--skeleton:hover{transform:none;box-shadow:0 1px 4px #1018280f}@keyframes osl-card-highlight-pulse{0%{box-shadow:0 0 0 4px #6366f14d}to{box-shadow:none}}.osl-card-inner{padding:12px 16px 12px 20px;display:flex;flex-direction:row;align-items:center;gap:0;min-height:0}.osl-card-header{display:flex;align-items:center;gap:12px;flex-shrink:0;width:220px;min-width:0;padding-right:16px}.osl-card-avatar{width:40px;height:40px;border-radius:10px;background:var(--card-accent, var(--osl-primary, #6366f1));color:#fff;font-size:16px;font-weight:700;display:flex;align-items:center;justify-content:center;flex-shrink:0;letter-spacing:0;box-shadow:0 4px 10px #00000026}.osl-card-avatar--skeleton{background:#e5e7eb;box-shadow:none;animation:osl-sk-pulse 1.4s ease-in-out infinite}.osl-card-title-wrap{flex:1;min-width:0}.osl-card-title{display:block;font-weight:700;font-size:14px;color:#101828;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;line-height:1.4}.osl-card-actions{display:flex;align-items:center;gap:6px;flex-shrink:0;margin-left:12px}.osl-card-action-btn{display:inline-flex;align-items:center;justify-content:center;width:30px;height:30px;border-radius:8px;border:1.5px solid #e4e7ec;background:#fff;cursor:pointer;transition:background .15s,border-color .15s,transform .15s,box-shadow .15s;padding:0}.osl-card-action-btn svg{width:14px;height:14px;transition:stroke .15s,fill .15s}.osl-card-action-btn--more svg{fill:#9ca3af;stroke:none}.osl-card-action-btn--more:hover{background:var(--card-accent-bg, #eef2ff);border-color:var(--card-accent, var(--osl-primary, #6366f1));box-shadow:0 2px 6px #00000014;transform:scale(1.05)}.osl-card-action-btn--more:hover svg{fill:var(--card-accent, var(--osl-primary, #6366f1))}.osl-card-action-btn--edit svg{stroke:#6b7280}.osl-card-action-btn--edit:hover{background:var(--card-accent-bg, #eef2ff);border-color:var(--card-accent, var(--osl-primary, #6366f1));box-shadow:0 2px 6px #00000014;transform:scale(1.05)}.osl-card-action-btn--edit:hover svg{stroke:var(--card-accent, var(--osl-primary, #6366f1))}.osl-card-action-btn--delete svg{stroke:#9ca3af}.osl-card-action-btn--delete:hover{background:#fff1f2;border-color:#f43f5e;box-shadow:0 2px 6px #f43f5e26;transform:scale(1.05)}.osl-card-action-btn--delete:hover svg{stroke:#f43f5e}.osl-card-divider{width:1px;align-self:stretch;background:#f2f4f7;margin:0 16px;flex-shrink:0}.osl-card-body{display:flex;flex-direction:row;align-items:center;gap:24px;flex:1;min-width:0;flex-wrap:wrap}.osl-card-field{display:flex;flex-direction:column;gap:3px;flex:1;min-width:100px;max-width:200px}.osl-card-label{font-size:10px;font-weight:600;color:#9ca3af;text-transform:uppercase;letter-spacing:.06em;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.osl-card-value{font-size:13px;font-weight:500;color:#374151;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}.osl-card-value.link{text-decoration:underline;color:#2563eb;cursor:pointer}.osl-card-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:320px;gap:12px;padding:40px 20px}.osl-card-empty-svg{width:180px;height:auto;opacity:.85}.osl-card-empty-title{font-size:16px;font-weight:600;color:#374151;margin:4px 0 0;text-align:center}.osl-card-empty-desc{font-size:13px;color:#9ca3af;margin:0;text-align:center}.osl-card-loader{display:flex;flex-direction:column;align-items:center;gap:10px;padding:28px 20px 16px}.osl-card-loader-track{width:160px;height:3px;background:#f3f4f6;border-radius:99px;overflow:hidden}.osl-card-loader-bar{height:100%;width:40%;background:linear-gradient(90deg,transparent,var(--osl-primary, #6366f1),transparent);border-radius:99px;animation:osl-loader-sweep 1.2s ease-in-out infinite}@keyframes osl-loader-sweep{0%{transform:translate(-200%)}to{transform:translate(500%)}}.osl-card-loader-text{font-size:12px;color:#9ca3af;font-weight:500}.osl-card-all-loaded{display:flex;align-items:center;gap:12px;padding:20px 16px 8px;justify-content:center}.osl-card-all-loaded-line{flex:1;max-width:80px;height:1px;background:#e5e7eb}.osl-card-all-loaded-text{font-size:11px;color:#d1d5db;font-weight:600;text-transform:uppercase;letter-spacing:.06em;white-space:nowrap}@keyframes osl-sk-pulse{0%,to{opacity:1}50%{opacity:.4}}.osl-sk-line{height:12px;background:#e9eaec;border-radius:6px;animation:osl-sk-pulse 1.4s ease-in-out infinite;width:80%}.osl-sk-line--title{height:14px;width:65%}.osl-sk-line--label{width:40%;height:10px}.osl-sk-line--short{width:50%}.osl-card-menu{position:fixed;z-index:9999;min-width:196px;background:#fff;border:1px solid #eaecf0;border-radius:12px;box-shadow:0 12px 32px #10182824,0 4px 12px #1018280f;overflow:hidden;animation:osl-menu-pop .16s cubic-bezier(.16,1,.3,1)}@keyframes osl-menu-pop{0%{opacity:0;transform:translateY(-8px) scale(.96)}to{opacity:1;transform:translateY(0) scale(1)}}.osl-card-menu-header{padding:10px 14px 7px;font-size:10px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:#9ca3af;border-bottom:1px solid #f2f4f7;-webkit-user-select:none;user-select:none}.osl-card-menu-item{display:flex;align-items:center;gap:10px;width:100%;padding:10px 14px;background:transparent;border:none;border-left:3px solid transparent;border-bottom:1px solid #f9fafb;text-align:left;font-size:13px;font-weight:500;font-family:inherit;color:#374151;cursor:pointer;white-space:nowrap;transition:background .12s,color .12s,border-left-color .12s}.osl-card-menu-item:last-child{border-bottom:none}.osl-card-menu-item:hover{background:var(--card-accent-bg, #f5f3ff);color:var(--card-accent, var(--osl-primary, #6366f1));border-left-color:var(--card-accent, var(--osl-primary, #6366f1))}.osl-card-menu-item:hover .osl-card-menu-dot{background:var(--card-accent, var(--osl-primary, #6366f1));box-shadow:0 0 0 3px #6366f129}.osl-card-menu-dot{flex-shrink:0;width:6px;height:6px;border-radius:50%;background:#d1d5db;transition:background .12s,box-shadow .12s}\n"], dependencies: [{ kind: "directive", type: i1$2.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i2$2.CdkFixedSizeVirtualScroll, selector: "cdk-virtual-scroll-viewport[itemSize]", inputs: ["itemSize", "minBufferPx", "maxBufferPx"] }, { kind: "directive", type: i2$2.CdkVirtualForOf, selector: "[cdkVirtualFor][cdkVirtualForOf]", inputs: ["cdkVirtualForOf", "cdkVirtualForTrackBy", "cdkVirtualForTemplate", "cdkVirtualForTemplateCacheSize"] }, { kind: "component", type: i2$2.CdkVirtualScrollViewport, selector: "cdk-virtual-scroll-viewport", inputs: ["orientation", "appendOnly"], outputs: ["scrolledIndexChange"] }, { kind: "component", type: DynamicForm, selector: "osl-dynamic-form", inputs: ["elements", "model", "skeletonLoading", "skeletonTheme"], outputs: ["modelChange"] }, { kind: "component", type: OslButton, selector: "osl-button", inputs: ["label", "icon", "variant", "size", "disabled", "loading", "type", "fullWidth"], outputs: ["clickEv"] }, { kind: "component", type: OslSearchbar, selector: "osl-searchbar", inputs: ["label"], outputs: ["onSearch"] }, { kind: "component", type: OslGrid, selector: "osl-grid", inputs: ["columns", "datasource", "isPaginated", "pageSize", "autoMode", "totalRecords", "tableHeight", "loading", "isSelectable", "moreMenuActions", "canEdit", "canDelete", "highlightedRow", "primaryKey"], outputs: ["datasourceChange", "pageChange", "pageSizeChange", "sortChange", "editClick", "deleteClick", "onRowClick"] }, { kind: "pipe", type: i1$2.DatePipe, name: "date" }] });
3982
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: OslSetup, isStandalone: false, selector: "osl-setup", inputs: { title: "title", columns: "columns", datasource: "datasource", isPaginated: "isPaginated", pageSize: "pageSize", autoMode: "autoMode", tableHeight: "tableHeight", totalRecords: "totalRecords", loading: "loading", dialogWidth: "dialogWidth", formElements: "formElements", beforeDisplay: "beforeDisplay", onAddEditFn: "onAddEditFn", isLister: "isLister", canAdd: "canAdd", canEdit: "canEdit", canDelete: "canDelete", moreMenuActions: "moreMenuActions", customFormFooter: "customFormFooter", customHeaderTemp: "customHeaderTemp", partialCustomHeaderTemp: "partialCustomHeaderTemp", stateKey: "stateKey", primaryKey: "primaryKey", onSave: "onSave", cardPageSize: "cardPageSize", cardTemplate: "cardTemplate", 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 <h5 class=\"mb-0\">{{ title }}</h5>\n <div class=\"d-flex align-items-center gap-2\">\n <osl-searchbar #searchbar class=\"mx-2\" (onSearch)=\"onSearchSetup($event)\"></osl-searchbar>\n\n <!-- View Toggle -->\n @if(!isLister){\n <div class=\"osl-view-toggle\">\n <button class=\"osl-view-toggle-btn\" [class.osl-view-toggle-btn--active]=\"viewMode === 'table'\"\n (click)=\"toggleView('table')\" title=\"Table view\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"2\"/>\n <line x1=\"3\" y1=\"9\" x2=\"21\" y2=\"9\"/>\n <line x1=\"3\" y1=\"15\" x2=\"21\" y2=\"15\"/>\n <line x1=\"9\" y1=\"9\" x2=\"9\" y2=\"21\"/>\n </svg>\n </button>\n <button class=\"osl-view-toggle-btn\" [class.osl-view-toggle-btn--active]=\"viewMode === 'card'\"\n (click)=\"toggleView('card')\" title=\"Card view\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <rect x=\"3\" y=\"3\" width=\"7\" height=\"7\" rx=\"1.5\"/>\n <rect x=\"14\" y=\"3\" width=\"7\" height=\"7\" rx=\"1.5\"/>\n <rect x=\"3\" y=\"14\" width=\"7\" height=\"7\" rx=\"1.5\"/>\n <rect x=\"14\" y=\"14\" width=\"7\" height=\"7\" rx=\"1.5\"/>\n </svg>\n </button>\n </div>\n }\n\n @if (!isLister && canAdd) {\n <osl-button variant=\"secondary\" size=\"sm\" [label]=\"'Add ' + title\" (clickEv)=\"openAddDialog()\"></osl-button>\n }\n </div>\n </div>\n\n <!-- \u2500\u2500 Table View \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (viewMode === 'table') {\n <div class=\"osl-setup-body my-2\">\n <osl-grid\n #gridRef\n [columns]=\"columnsWithActions\"\n [(datasource)]=\"datasource\"\n [isPaginated]=\"isPaginated\"\n [pageSize]=\"pageSize\"\n [autoMode]=\"autoMode\"\n [tableHeight]=\"tableHeight\"\n [totalRecords]=\"totalRecords\"\n [loading]=\"loading\"\n [moreMenuActions]=\"moreMenuActions\"\n [canEdit]=\"canEdit\"\n [canDelete]=\"canDelete\"\n [highlightedRow]=\"restoredRow\"\n [primaryKey]=\"primaryKey\"\n (editClick)=\"openEditDialog($event)\"\n (deleteClick)=\"onDeleteClick($event)\"\n (pageChange)=\"onPageChange(pageChange, $event)\"\n (pageSizeChange)=\"onPageChange(pageSizeChange, $event)\"\n (sortChange)=\"sortChange.emit($event)\"\n [isSelectable]=\"isLister\"\n (onRowClick)=\"onRowClick.emit($event)\"\n />\n </div>\n }\n\n <!-- \u2500\u2500 Card View \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (viewMode === 'card') {\n\n <!-- Skeleton on initial load -->\n @if (loading && 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)}\n"], dependencies: [{ kind: "directive", type: i1$2.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i1$1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1$1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1$1.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.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$2.DatePipe, name: "date" }] });
3877
3983
  }
3878
3984
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: OslSetup, decorators: [{
3879
3985
  type: Component,
3880
- args: [{ selector: 'osl-setup', standalone: false, template: "<div class=\"p-2\">\n\n <!-- \u2500\u2500 Header \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div class=\"osl-setup-header\">\n <h5 class=\"mb-0\">{{ title }}</h5>\n <div class=\"d-flex align-items-center gap-2\">\n <osl-searchbar #searchbar class=\"mx-2\" (onSearch)=\"onSearchSetup($event)\"></osl-searchbar>\n\n <!-- View Toggle -->\n @if(!isLister){\n <div class=\"osl-view-toggle\">\n <button class=\"osl-view-toggle-btn\" [class.osl-view-toggle-btn--active]=\"viewMode === 'table'\"\n (click)=\"toggleView('table')\" title=\"Table view\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"2\"/>\n <line x1=\"3\" y1=\"9\" x2=\"21\" y2=\"9\"/>\n <line x1=\"3\" y1=\"15\" x2=\"21\" y2=\"15\"/>\n <line x1=\"9\" y1=\"9\" x2=\"9\" y2=\"21\"/>\n </svg>\n </button>\n <button class=\"osl-view-toggle-btn\" [class.osl-view-toggle-btn--active]=\"viewMode === 'card'\"\n (click)=\"toggleView('card')\" title=\"Card view\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <rect x=\"3\" y=\"3\" width=\"7\" height=\"7\" rx=\"1.5\"/>\n <rect x=\"14\" y=\"3\" width=\"7\" height=\"7\" rx=\"1.5\"/>\n <rect x=\"3\" y=\"14\" width=\"7\" height=\"7\" rx=\"1.5\"/>\n <rect x=\"14\" y=\"14\" width=\"7\" height=\"7\" rx=\"1.5\"/>\n </svg>\n </button>\n </div>\n }\n\n @if (!isLister && canAdd) {\n <osl-button variant=\"secondary\" size=\"sm\" [label]=\"'Add ' + title\" (clickEv)=\"openAddDialog()\"></osl-button>\n }\n </div>\n </div>\n\n <!-- \u2500\u2500 Table View \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (viewMode === 'table') {\n <div class=\"osl-setup-body my-2\">\n <osl-grid\n #gridRef\n [columns]=\"columnsWithActions\"\n [(datasource)]=\"datasource\"\n [isPaginated]=\"isPaginated\"\n [pageSize]=\"pageSize\"\n [autoMode]=\"autoMode\"\n [tableHeight]=\"tableHeight\"\n [totalRecords]=\"totalRecords\"\n [loading]=\"loading\"\n [moreMenuActions]=\"moreMenuActions\"\n [canEdit]=\"canEdit\"\n [canDelete]=\"canDelete\"\n [highlightedRow]=\"restoredRow\"\n [primaryKey]=\"primaryKey\"\n (editClick)=\"openEditDialog($event)\"\n (deleteClick)=\"onDeleteClick($event)\"\n (pageChange)=\"onPageChange(pageChange, $event)\"\n (pageSizeChange)=\"onPageChange(pageSizeChange, $event)\"\n (sortChange)=\"sortChange.emit($event)\"\n [isSelectable]=\"isLister\"\n (onRowClick)=\"onRowClick.emit($event)\"\n />\n </div>\n }\n\n <!-- \u2500\u2500 Card View \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (viewMode === 'card') {\n\n <!-- Skeleton on initial load -->\n @if (loading && cardDatasource.length === 0) {\n <div class=\"osl-card-container my-2\" [style.height]=\"tableHeight\">\n <div class=\"osl-card-grid\">\n @for (sk of skeletonCardRows; track $index) {\n <div class=\"osl-card osl-card--skeleton\" [style.height.px]=\"cardHeightForVirtualscroll\">\n <div class=\"osl-card-inner\">\n <div class=\"osl-card-header\">\n <div class=\"osl-card-avatar osl-card-avatar--skeleton\"></div>\n <div style=\"flex:1;min-width:0\">\n <div class=\"osl-sk-line osl-sk-line--title\"></div>\n </div>\n </div>\n <div class=\"osl-card-divider\"></div>\n <div class=\"osl-card-body\">\n <div class=\"osl-card-field\"><div class=\"osl-sk-line osl-sk-line--label\"></div><div class=\"osl-sk-line\"></div></div>\n <div class=\"osl-card-field\"><div class=\"osl-sk-line osl-sk-line--label\"></div><div class=\"osl-sk-line osl-sk-line--short\"></div></div>\n <div class=\"osl-card-field\"><div class=\"osl-sk-line osl-sk-line--label\"></div><div class=\"osl-sk-line\"></div></div>\n </div>\n </div>\n </div>\n }\n </div>\n </div>\n }\n\n <!-- Empty state -->\n @else if (cardDatasource.length === 0 && !loading) {\n <div class=\"osl-card-container my-2\" [style.height]=\"tableHeight\">\n <div class=\"osl-card-empty\">\n <svg class=\"osl-card-empty-svg\" viewBox=\"0 0 180 160\" fill=\"none\">\n <ellipse cx=\"90\" cy=\"140\" rx=\"60\" ry=\"8\" fill=\"#f3f4f6\"/>\n <rect x=\"30\" y=\"30\" width=\"120\" height=\"96\" rx=\"10\" fill=\"#f9fafb\" stroke=\"#e5e7eb\" stroke-width=\"2\"/>\n <rect x=\"44\" y=\"48\" width=\"92\" height=\"10\" rx=\"5\" fill=\"#e5e7eb\"/>\n <rect x=\"44\" y=\"66\" width=\"72\" height=\"8\" rx=\"4\" fill=\"#f3f4f6\"/>\n <rect x=\"44\" y=\"82\" width=\"84\" height=\"8\" rx=\"4\" fill=\"#f3f4f6\"/>\n <rect x=\"44\" y=\"98\" width=\"56\" height=\"8\" rx=\"4\" fill=\"#f3f4f6\"/>\n <circle cx=\"140\" cy=\"108\" r=\"26\" fill=\"#fff\" stroke=\"#e5e7eb\" stroke-width=\"2\"/>\n <line x1=\"133\" y1=\"101\" x2=\"147\" y2=\"115\" stroke=\"#d1d5db\" stroke-width=\"2.5\" stroke-linecap=\"round\"/>\n <line x1=\"147\" y1=\"101\" x2=\"133\" y2=\"115\" stroke=\"#d1d5db\" stroke-width=\"2.5\" stroke-linecap=\"round\"/>\n </svg>\n <p class=\"osl-card-empty-title\">No records found</p>\n <p class=\"osl-card-empty-desc\">Try adjusting your search or filter criteria</p>\n </div>\n </div>\n }\n\n <!-- Virtual scroll card list -->\n @else {\n <div class=\"osl-card-vs-wrapper my-2\" [style.height]=\"tableHeight\">\n <cdk-virtual-scroll-viewport\n #cardViewport\n [itemSize]=\"cardHeightForVirtualscroll\"\n class=\"osl-card-virtual-viewport\"\n (scrolledIndexChange)=\"onVirtualScrollIndexChange($event)\">\n\n <div *cdkVirtualFor=\"let row of cardDatasource; trackBy: trackByCard; let i = index\"\n class=\"osl-card-vs-row\"\n [style.height.px]=\"cardHeightForVirtualscroll\">\n\n @if (cardTemplate) {\n <ng-container *ngTemplateOutlet=\"cardTemplate; context: { $implicit: row, index: i }\"></ng-container>\n } @else {\n <div class=\"osl-card\"\n [class.osl-card--highlighted]=\"isHighlightedCard(row)\"\n [class.osl-card--selectable]=\"isLister\"\n (click)=\"isLister ? onRowClick.emit(row) : null\">\n\n <div class=\"osl-card-inner\">\n\n <!-- Left: avatar + title -->\n <div class=\"osl-card-header\">\n <!-- @if (cardTitleColumn) {\n <div class=\"osl-card-avatar\">{{ getCardInitial(row) }}</div>\n } -->\n <div class=\"osl-card-title-wrap\">\n <span class=\"osl-card-title\">\n @if (cardTitleColumn) {\n @switch (cardTitleColumn.displayType) {\n @case ('date') { {{ row[cardTitleColumn.key] | date }} }\n @case ('datetime') { {{ row[cardTitleColumn.key] | date:'medium' }} }\n @case ('time') { {{ row[cardTitleColumn.key] | date:'shortTime' }} }\n @case ('customDateFormat') { {{ row[cardTitleColumn.key] | date:cardTitleColumn.customDateFormat }} }\n @default { {{ getCellValue(row, cardTitleColumn) }} }\n }\n }\n </span>\n </div>\n </div>\n\n <!-- Vertical divider -->\n <div class=\"osl-card-divider\"></div>\n\n <!-- Middle: body fields (horizontal) -->\n <div class=\"osl-card-body\">\n @for (col of cardBodyColumns; track col.key) {\n <div class=\"osl-card-field\">\n <span class=\"osl-card-label\">{{ col.label }}</span>\n <span class=\"osl-card-value\" [class.link]=\"col.displayType === 'link'\" (click)=\"col.click ? col.click(row, col) : null\">\n @switch (col.displayType) {\n @case ('date') { {{ row[col.key] | date }} }\n @case ('datetime') { {{ row[col.key] | date:'medium' }} }\n @case ('time') { {{ row[col.key] | date:'shortTime' }} }\n @case ('customDateFormat') { {{ row[col.key] | date:col.customDateFormat }} }\n @default { {{ getCellValue(row, col) }} }\n }\n </span>\n </div>\n }\n </div>\n\n <!-- Right: action buttons -->\n @if (!isLister && ((hasForm && (canEdit || canDelete)) || (moreMenuActions.length > 0 && hasVisibleActions(row)))) {\n <div class=\"osl-card-actions\">\n \n \n @if (hasForm && canDelete) {\n <button class=\"osl-card-action-btn osl-card-action-btn--delete\"\n (click)=\"$event.stopPropagation(); onDeleteClick(row)\" title=\"Delete\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polyline points=\"3 6 5 6 21 6\"/>\n <path d=\"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2\"/>\n </svg>\n </button>\n }\n </div>\n }\n @if (hasForm && canEdit) {\n <button class=\"osl-card-action-btn osl-card-action-btn--edit\"\n (click)=\"$event.stopPropagation(); openEditDialog(row)\" title=\"Edit\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7\"/>\n <path d=\"M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z\"/>\n </svg>\n </button>\n }\n\n @if (moreMenuActions.length > 0 && hasVisibleActions(row)) {\n <button class=\"osl-card-action-btn osl-card-action-btn--more\"\n (click)=\"toggleCardMenu(i, $event)\" title=\"More actions\">\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <circle cx=\"12\" cy=\"5\" r=\"2.2\"/>\n <circle cx=\"12\" cy=\"12\" r=\"2.2\"/>\n <circle cx=\"12\" cy=\"19\" r=\"2.2\"/>\n </svg>\n </button>\n }\n\n </div>\n </div>\n }\n </div>\n </cdk-virtual-scroll-viewport>\n </div>\n\n <!-- Infinite scroll loader (outside viewport) -->\n @if (loading) {\n <div class=\"osl-card-loader\">\n <div class=\"osl-card-loader-track\">\n <div class=\"osl-card-loader-bar\"></div>\n </div>\n <span class=\"osl-card-loader-text\">Loading more\u2026</span>\n </div>\n }\n\n <!-- All loaded footer (outside viewport) -->\n @if (allCardsLoaded && !loading && cardDatasource.length > 0) {\n <div class=\"osl-card-all-loaded\">\n <span class=\"osl-card-all-loaded-line\"></span>\n <span class=\"osl-card-all-loaded-text\">All {{ totalRecords || cardDatasource.length }} records loaded</span>\n <span class=\"osl-card-all-loaded-line\"></span>\n </div>\n }\n }\n }\n\n</div>\n\n<!-- Dialog: Form Body -->\n<ng-template #formBodyTpl>\n <osl-dynamic-form\n [skeletonLoading]=\"formLoading\"\n [elements]=\"formElements\"\n [(model)]=\"dialogModel\"\n ></osl-dynamic-form>\n</ng-template>\n\n<!-- Dialog: Form Footer -->\n<ng-template #formFooterTpl>\n <div class=\"osl-setup-dialog-footer\">\n <osl-button [loading]=\"saveLoading\" variant=\"secondary\" label=\"Save\" (click)=\"saveDialog()\"></osl-button>\n </div>\n</ng-template>\n\n<ng-template #customFooterWrapperTpl let-data>\n <ng-container *ngTemplateOutlet=\"customFormFooter!; context: { $implicit: { dialogModel: dialogModel, dialogMode: dialogMode, dialogRef: data.dialogRef } }\"></ng-container>\n</ng-template>\n\n<!-- Floating card more-actions menu \u2014 rendered outside card DOM to avoid transform containment -->\n@if (viewMode === 'card' && cardOpenMenuIndex !== null && moreMenuActions.length > 0) {\n <div class=\"osl-card-menu\"\n [style.top.px]=\"cardMenuPosition.top\"\n [style.left.px]=\"cardMenuPosition.left\"\n (click)=\"$event.stopPropagation()\">\n <div class=\"osl-card-menu-header\">Actions</div>\n @for (action of moreMenuActions; track $index) {\n @if (!action.hideIf || !action.hideIf(cardDatasource[cardOpenMenuIndex])) {\n <button class=\"osl-card-menu-item\"\n (click)=\"action.click(cardDatasource[cardOpenMenuIndex]); cardOpenMenuIndex = null\">\n <span class=\"osl-card-menu-dot\"></span>\n {{ action.labelIf ? action.labelIf(cardDatasource[cardOpenMenuIndex]) : action.label }}\n </button>\n }\n }\n </div>\n}\n", styles: [".osl-setup-header{display:flex;align-items:center;justify-content:space-between}.osl-setup-dialog-footer{display:flex;align-items:center;justify-content:flex-end;gap:10px;width:100%}.dialog-cancel-btn{display:flex;align-items:center;gap:6px;height:38px;font-size:13px;font-weight:500;border-color:var(--osl-border-color, #d1d5db);color:var(--osl-secondary, #6b7280);border-radius:var(--osl-border-radius, 4px);padding:0 16px;transition:all .2s ease}.dialog-cancel-btn mat-icon{font-size:17px;width:17px;height:17px}.dialog-cancel-btn:hover{border-color:#9ca3af;background:#f9fafb;color:#374151}.dialog-save-btn{display:flex;align-items:center;gap:6px;height:38px;font-size:13px;font-weight:500;background:linear-gradient(135deg,var(--osl-primary, #2563eb),#3b82f6);color:#fff;border-radius:var(--osl-border-radius, 4px);padding:0 18px;transition:all .2s ease;box-shadow:0 2px 8px #2563eb40}.dialog-save-btn mat-icon{font-size:17px;width:17px;height:17px}.dialog-save-btn:hover{background:linear-gradient(135deg,var(--osl-primary-hover, #1d4ed8),#2563eb);box-shadow:0 4px 14px #2563eb66;transform:translateY(-1px)}.dialog-save-btn:active{transform:translateY(0);box-shadow:0 2px 8px #2563eb40}.osl-view-toggle{display:flex;border:1.5px solid var(--osl-border-color, #e5e7eb);border-radius:8px;overflow:hidden;flex-shrink:0;background:#fff}.osl-view-toggle-btn{display:inline-flex;align-items:center;justify-content:center;width:34px;height:34px;border:none;background:transparent;cursor:pointer;color:#9ca3af;transition:background .15s,color .15s;padding:0}.osl-view-toggle-btn svg{width:16px;height:16px;transition:stroke .15s}.osl-view-toggle-btn:first-child{border-right:1.5px solid var(--osl-border-color, #e5e7eb)}.osl-view-toggle-btn--active{background:var(--osl-primary, #6366f1);color:#fff}.osl-view-toggle-btn--active svg{stroke:#fff}.osl-view-toggle-btn:not(.osl-view-toggle-btn--active):hover{background:#f3f4f6;color:#374151}.osl-card-container{overflow-y:auto;overflow-x:hidden;border-radius:12px;padding:4px 2px 16px;box-sizing:border-box}.osl-card-vs-wrapper{display:flex;flex-direction:column;overflow:hidden;box-sizing:border-box}.osl-card-virtual-viewport{flex:1;min-height:0;width:100%}.osl-card-vs-row{padding:0 2px 4px;box-sizing:border-box}.osl-card-vs-row>.osl-card{height:100%}.osl-card-grid{display:flex;flex-direction:column;gap:10px;padding:4px}@keyframes osl-card-enter{0%{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}.osl-card-grid .osl-card:nth-child(1){animation-delay:0s}.osl-card-grid .osl-card:nth-child(2){animation-delay:.03s}.osl-card-grid .osl-card:nth-child(3){animation-delay:.06s}.osl-card-grid .osl-card:nth-child(4){animation-delay:.09s}.osl-card-grid .osl-card:nth-child(5){animation-delay:.12s}.osl-card-grid .osl-card:nth-child(6){animation-delay:.15s}.osl-card-grid .osl-card:nth-child(7){animation-delay:.18s}.osl-card-grid .osl-card:nth-child(8){animation-delay:.21s}.osl-card{position:relative;background:#fff;border-radius:14px;border:1px solid #eaecf0;box-shadow:0 1px 4px #1018280f,0 1px 2px #1018280a;overflow:hidden;transition:transform .22s cubic-bezier(.34,1.56,.64,1),box-shadow .22s ease;animation:osl-card-enter .28s ease both}.osl-card:nth-child(6n+1){--card-accent: #6366f1;--card-accent-bg: #eef2ff}.osl-card:nth-child(6n+2){--card-accent: #0ea5e9;--card-accent-bg: #f0f9ff}.osl-card:nth-child(6n+3){--card-accent: #10b981;--card-accent-bg: #ecfdf5}.osl-card:nth-child(6n+4){--card-accent: #f59e0b;--card-accent-bg: #fffbeb}.osl-card:nth-child(6n+5){--card-accent: #f43f5e;--card-accent-bg: #fff1f2}.osl-card:nth-child(6n){--card-accent: #8b5cf6;--card-accent-bg: #faf5ff}.osl-card:before{content:\"\";position:absolute;top:0;left:0;bottom:0;width:4px;background:var(--card-accent, var(--osl-primary, #6366f1))}.osl-card:hover{transform:translateY(-2px);box-shadow:0 6px 20px #10182817,0 2px 8px #1018280d;border-color:#d0d5dd}.osl-card--highlighted{outline:2px solid var(--osl-primary, #6366f1);outline-offset:2px;animation:osl-card-enter .28s ease both,osl-card-highlight-pulse 2.5s ease-out .3s both}.osl-card--selectable{cursor:pointer}.osl-card--skeleton{pointer-events:none;animation:osl-card-enter .28s ease both}.osl-card--skeleton:hover{transform:none;box-shadow:0 1px 4px #1018280f}@keyframes osl-card-highlight-pulse{0%{box-shadow:0 0 0 4px #6366f14d}to{box-shadow:none}}.osl-card-inner{padding:12px 16px 12px 20px;display:flex;flex-direction:row;align-items:center;gap:0;min-height:0}.osl-card-header{display:flex;align-items:center;gap:12px;flex-shrink:0;width:220px;min-width:0;padding-right:16px}.osl-card-avatar{width:40px;height:40px;border-radius:10px;background:var(--card-accent, var(--osl-primary, #6366f1));color:#fff;font-size:16px;font-weight:700;display:flex;align-items:center;justify-content:center;flex-shrink:0;letter-spacing:0;box-shadow:0 4px 10px #00000026}.osl-card-avatar--skeleton{background:#e5e7eb;box-shadow:none;animation:osl-sk-pulse 1.4s ease-in-out infinite}.osl-card-title-wrap{flex:1;min-width:0}.osl-card-title{display:block;font-weight:700;font-size:14px;color:#101828;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;line-height:1.4}.osl-card-actions{display:flex;align-items:center;gap:6px;flex-shrink:0;margin-left:12px}.osl-card-action-btn{display:inline-flex;align-items:center;justify-content:center;width:30px;height:30px;border-radius:8px;border:1.5px solid #e4e7ec;background:#fff;cursor:pointer;transition:background .15s,border-color .15s,transform .15s,box-shadow .15s;padding:0}.osl-card-action-btn svg{width:14px;height:14px;transition:stroke .15s,fill .15s}.osl-card-action-btn--more svg{fill:#9ca3af;stroke:none}.osl-card-action-btn--more:hover{background:var(--card-accent-bg, #eef2ff);border-color:var(--card-accent, var(--osl-primary, #6366f1));box-shadow:0 2px 6px #00000014;transform:scale(1.05)}.osl-card-action-btn--more:hover svg{fill:var(--card-accent, var(--osl-primary, #6366f1))}.osl-card-action-btn--edit svg{stroke:#6b7280}.osl-card-action-btn--edit:hover{background:var(--card-accent-bg, #eef2ff);border-color:var(--card-accent, var(--osl-primary, #6366f1));box-shadow:0 2px 6px #00000014;transform:scale(1.05)}.osl-card-action-btn--edit:hover svg{stroke:var(--card-accent, var(--osl-primary, #6366f1))}.osl-card-action-btn--delete svg{stroke:#9ca3af}.osl-card-action-btn--delete:hover{background:#fff1f2;border-color:#f43f5e;box-shadow:0 2px 6px #f43f5e26;transform:scale(1.05)}.osl-card-action-btn--delete:hover svg{stroke:#f43f5e}.osl-card-divider{width:1px;align-self:stretch;background:#f2f4f7;margin:0 16px;flex-shrink:0}.osl-card-body{display:flex;flex-direction:row;align-items:center;gap:24px;flex:1;min-width:0;flex-wrap:wrap}.osl-card-field{display:flex;flex-direction:column;gap:3px;flex:1;min-width:100px;max-width:200px}.osl-card-label{font-size:10px;font-weight:600;color:#9ca3af;text-transform:uppercase;letter-spacing:.06em;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.osl-card-value{font-size:13px;font-weight:500;color:#374151;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}.osl-card-value.link{text-decoration:underline;color:#2563eb;cursor:pointer}.osl-card-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:320px;gap:12px;padding:40px 20px}.osl-card-empty-svg{width:180px;height:auto;opacity:.85}.osl-card-empty-title{font-size:16px;font-weight:600;color:#374151;margin:4px 0 0;text-align:center}.osl-card-empty-desc{font-size:13px;color:#9ca3af;margin:0;text-align:center}.osl-card-loader{display:flex;flex-direction:column;align-items:center;gap:10px;padding:28px 20px 16px}.osl-card-loader-track{width:160px;height:3px;background:#f3f4f6;border-radius:99px;overflow:hidden}.osl-card-loader-bar{height:100%;width:40%;background:linear-gradient(90deg,transparent,var(--osl-primary, #6366f1),transparent);border-radius:99px;animation:osl-loader-sweep 1.2s ease-in-out infinite}@keyframes osl-loader-sweep{0%{transform:translate(-200%)}to{transform:translate(500%)}}.osl-card-loader-text{font-size:12px;color:#9ca3af;font-weight:500}.osl-card-all-loaded{display:flex;align-items:center;gap:12px;padding:20px 16px 8px;justify-content:center}.osl-card-all-loaded-line{flex:1;max-width:80px;height:1px;background:#e5e7eb}.osl-card-all-loaded-text{font-size:11px;color:#d1d5db;font-weight:600;text-transform:uppercase;letter-spacing:.06em;white-space:nowrap}@keyframes osl-sk-pulse{0%,to{opacity:1}50%{opacity:.4}}.osl-sk-line{height:12px;background:#e9eaec;border-radius:6px;animation:osl-sk-pulse 1.4s ease-in-out infinite;width:80%}.osl-sk-line--title{height:14px;width:65%}.osl-sk-line--label{width:40%;height:10px}.osl-sk-line--short{width:50%}.osl-card-menu{position:fixed;z-index:9999;min-width:196px;background:#fff;border:1px solid #eaecf0;border-radius:12px;box-shadow:0 12px 32px #10182824,0 4px 12px #1018280f;overflow:hidden;animation:osl-menu-pop .16s cubic-bezier(.16,1,.3,1)}@keyframes osl-menu-pop{0%{opacity:0;transform:translateY(-8px) scale(.96)}to{opacity:1;transform:translateY(0) scale(1)}}.osl-card-menu-header{padding:10px 14px 7px;font-size:10px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:#9ca3af;border-bottom:1px solid #f2f4f7;-webkit-user-select:none;user-select:none}.osl-card-menu-item{display:flex;align-items:center;gap:10px;width:100%;padding:10px 14px;background:transparent;border:none;border-left:3px solid transparent;border-bottom:1px solid #f9fafb;text-align:left;font-size:13px;font-weight:500;font-family:inherit;color:#374151;cursor:pointer;white-space:nowrap;transition:background .12s,color .12s,border-left-color .12s}.osl-card-menu-item:last-child{border-bottom:none}.osl-card-menu-item:hover{background:var(--card-accent-bg, #f5f3ff);color:var(--card-accent, var(--osl-primary, #6366f1));border-left-color:var(--card-accent, var(--osl-primary, #6366f1))}.osl-card-menu-item:hover .osl-card-menu-dot{background:var(--card-accent, var(--osl-primary, #6366f1));box-shadow:0 0 0 3px #6366f129}.osl-card-menu-dot{flex-shrink:0;width:6px;height:6px;border-radius:50%;background:#d1d5db;transition:background .12s,box-shadow .12s}\n"] }]
3986
+ args: [{ selector: 'osl-setup', standalone: false, template: "<div class=\"p-2\">\n\n <!-- \u2500\u2500 Header \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div class=\"osl-setup-header\">\n <h5 class=\"mb-0\">{{ title }}</h5>\n <div class=\"d-flex align-items-center gap-2\">\n <osl-searchbar #searchbar class=\"mx-2\" (onSearch)=\"onSearchSetup($event)\"></osl-searchbar>\n\n <!-- View Toggle -->\n @if(!isLister){\n <div class=\"osl-view-toggle\">\n <button class=\"osl-view-toggle-btn\" [class.osl-view-toggle-btn--active]=\"viewMode === 'table'\"\n (click)=\"toggleView('table')\" title=\"Table view\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"2\"/>\n <line x1=\"3\" y1=\"9\" x2=\"21\" y2=\"9\"/>\n <line x1=\"3\" y1=\"15\" x2=\"21\" y2=\"15\"/>\n <line x1=\"9\" y1=\"9\" x2=\"9\" y2=\"21\"/>\n </svg>\n </button>\n <button class=\"osl-view-toggle-btn\" [class.osl-view-toggle-btn--active]=\"viewMode === 'card'\"\n (click)=\"toggleView('card')\" title=\"Card view\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <rect x=\"3\" y=\"3\" width=\"7\" height=\"7\" rx=\"1.5\"/>\n <rect x=\"14\" y=\"3\" width=\"7\" height=\"7\" rx=\"1.5\"/>\n <rect x=\"3\" y=\"14\" width=\"7\" height=\"7\" rx=\"1.5\"/>\n <rect x=\"14\" y=\"14\" width=\"7\" height=\"7\" rx=\"1.5\"/>\n </svg>\n </button>\n </div>\n }\n\n @if (!isLister && canAdd) {\n <osl-button variant=\"secondary\" size=\"sm\" [label]=\"'Add ' + title\" (clickEv)=\"openAddDialog()\"></osl-button>\n }\n </div>\n </div>\n\n <!-- \u2500\u2500 Table View \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (viewMode === 'table') {\n <div class=\"osl-setup-body my-2\">\n <osl-grid\n #gridRef\n [columns]=\"columnsWithActions\"\n [(datasource)]=\"datasource\"\n [isPaginated]=\"isPaginated\"\n [pageSize]=\"pageSize\"\n [autoMode]=\"autoMode\"\n [tableHeight]=\"tableHeight\"\n [totalRecords]=\"totalRecords\"\n [loading]=\"loading\"\n [moreMenuActions]=\"moreMenuActions\"\n [canEdit]=\"canEdit\"\n [canDelete]=\"canDelete\"\n [highlightedRow]=\"restoredRow\"\n [primaryKey]=\"primaryKey\"\n (editClick)=\"openEditDialog($event)\"\n (deleteClick)=\"onDeleteClick($event)\"\n (pageChange)=\"onPageChange(pageChange, $event)\"\n (pageSizeChange)=\"onPageChange(pageSizeChange, $event)\"\n (sortChange)=\"sortChange.emit($event)\"\n [isSelectable]=\"isLister\"\n (onRowClick)=\"onRowClick.emit($event)\"\n />\n </div>\n }\n\n <!-- \u2500\u2500 Card View \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (viewMode === 'card') {\n\n <!-- Skeleton on initial load -->\n @if (loading && 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)}\n"] }]
3881
3987
  }], propDecorators: { formBodyTpl: [{
3882
3988
  type: ViewChild,
3883
3989
  args: ['formBodyTpl']
@@ -3893,9 +3999,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
3893
3999
  }], gridRef: [{
3894
4000
  type: ViewChild,
3895
4001
  args: ['gridRef']
3896
- }], cardViewport: [{
4002
+ }], cardGridRef: [{
3897
4003
  type: ViewChild,
3898
- args: ['cardViewport']
4004
+ args: ['cardGridRef']
3899
4005
  }], title: [{
3900
4006
  type: Input,
3901
4007
  args: ['title']
@@ -3974,9 +4080,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
3974
4080
  }], cardTemplate: [{
3975
4081
  type: Input,
3976
4082
  args: ['cardTemplate']
3977
- }], cardHeightForVirtualscroll: [{
4083
+ }], cardCol: [{
3978
4084
  type: Input,
3979
- args: ['cardHeightForVirtualscroll']
4085
+ args: ['cardCol']
3980
4086
  }], onSearch: [{
3981
4087
  type: Output
3982
4088
  }], onAdd: [{
@@ -4229,7 +4335,7 @@ class OslAutocompleteLister {
4229
4335
  this.dialogRef.close(event);
4230
4336
  }
4231
4337
  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 });
4232
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.9", type: OslAutocompleteLister, isStandalone: false, selector: "osl-autocomplete-lister", inputs: { data: "data" }, viewQueries: [{ propertyName: "setup", first: true, predicate: ["setup"], descendants: true }], ngImport: i0, template: "<div class=\"p-4\">\r\n <osl-setup #setup [loading]=\"loader\" [autoMode]=\"false\" [title]=\"autocompleteData?.title\" (sortChange)=\"onSortChange($event)\" [totalRecords]=\"recordCount\" (pageSizeChange)=\"onPageChange($event)\" (pageChange)=\"onPageChange($event)\" (onRowClick)=\"onRowClick($event)\" [columns]=\"column\" [datasource]=\"datasource\" [isLister]=\"true\"></osl-setup>\r\n \r\n</div>", styles: [""], dependencies: [{ kind: "component", type: OslSetup, selector: "osl-setup", inputs: ["title", "columns", "datasource", "isPaginated", "pageSize", "autoMode", "tableHeight", "totalRecords", "loading", "dialogWidth", "formElements", "beforeDisplay", "onAddEditFn", "isLister", "canAdd", "canEdit", "canDelete", "moreMenuActions", "customFormFooter", "customHeaderTemp", "partialCustomHeaderTemp", "stateKey", "primaryKey", "onSave", "cardPageSize", "cardTemplate", "cardHeightForVirtualscroll"], outputs: ["onSearch", "onAdd", "onEdit", "onDelete", "pageChange", "pageSizeChange", "sortChange", "onRowClick", "onStateRestored"] }] });
4338
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.9", type: OslAutocompleteLister, isStandalone: false, selector: "osl-autocomplete-lister", inputs: { data: "data" }, viewQueries: [{ propertyName: "setup", first: true, predicate: ["setup"], descendants: true }], ngImport: i0, template: "<div class=\"p-4\">\r\n <osl-setup #setup [loading]=\"loader\" [autoMode]=\"false\" [title]=\"autocompleteData?.title\" (sortChange)=\"onSortChange($event)\" [totalRecords]=\"recordCount\" (pageSizeChange)=\"onPageChange($event)\" (pageChange)=\"onPageChange($event)\" (onRowClick)=\"onRowClick($event)\" [columns]=\"column\" [datasource]=\"datasource\" [isLister]=\"true\"></osl-setup>\r\n \r\n</div>", styles: [""], dependencies: [{ kind: "component", type: OslSetup, selector: "osl-setup", inputs: ["title", "columns", "datasource", "isPaginated", "pageSize", "autoMode", "tableHeight", "totalRecords", "loading", "dialogWidth", "formElements", "beforeDisplay", "onAddEditFn", "isLister", "canAdd", "canEdit", "canDelete", "moreMenuActions", "customFormFooter", "customHeaderTemp", "partialCustomHeaderTemp", "stateKey", "primaryKey", "onSave", "cardPageSize", "cardTemplate", "cardCol"], outputs: ["onSearch", "onAdd", "onEdit", "onDelete", "pageChange", "pageSizeChange", "sortChange", "onRowClick", "onStateRestored"] }] });
4233
4339
  }
4234
4340
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: OslAutocompleteLister, decorators: [{
4235
4341
  type: Component,
@@ -4255,201 +4361,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
4255
4361
  }]
4256
4362
  }] });
4257
4363
 
4258
- const TOOLTIP_CSS = `
4259
- .osl-tooltip {
4260
- position: fixed;
4261
- top: -9999px;
4262
- left: -9999px;
4263
- z-index: 10000;
4264
- background: #111827;
4265
- color: #ffffff;
4266
- font-size: 12px;
4267
- line-height: 1.55;
4268
- font-family: inherit;
4269
- padding: 6px 12px;
4270
- border-radius: 8px;
4271
- word-break: break-word;
4272
- white-space: pre-wrap;
4273
- box-shadow: 0 8px 24px rgba(0,0,0,0.22), 0 2px 6px rgba(0,0,0,0.14);
4274
- pointer-events: none;
4275
- }
4276
- .osl-tooltip::after {
4277
- content: '';
4278
- position: absolute;
4279
- border: 5px solid transparent;
4280
- }
4281
- .osl-tooltip--top::after {
4282
- top: 100%;
4283
- left: 50%;
4284
- transform: translateX(-50%);
4285
- border-top-color: #111827;
4286
- }
4287
- .osl-tooltip--bottom::after {
4288
- bottom: 100%;
4289
- left: 50%;
4290
- transform: translateX(-50%);
4291
- border-bottom-color: #111827;
4292
- }
4293
- .osl-tooltip--left::after {
4294
- top: 50%;
4295
- left: 100%;
4296
- transform: translateY(-50%);
4297
- border-left-color: #111827;
4298
- }
4299
- .osl-tooltip--right::after {
4300
- top: 50%;
4301
- right: 100%;
4302
- transform: translateY(-50%);
4303
- border-right-color: #111827;
4304
- }
4305
-
4306
- /* Default: position-aware slide animations */
4307
- .osl-tooltip--top { animation: _osl_tip_up 0.18s cubic-bezier(0.16,1,0.3,1) forwards; }
4308
- .osl-tooltip--bottom { animation: _osl_tip_down 0.18s cubic-bezier(0.16,1,0.3,1) forwards; }
4309
- .osl-tooltip--left { animation: _osl_tip_left 0.18s cubic-bezier(0.16,1,0.3,1) forwards; }
4310
- .osl-tooltip--right { animation: _osl_tip_right 0.18s cubic-bezier(0.16,1,0.3,1) forwards; }
4311
-
4312
- /* Animation override variants */
4313
- .osl-tooltip--anim-scale { animation: _osl_tip_scale 0.20s cubic-bezier(0.34,1.56,0.64,1) forwards !important; }
4314
- .osl-tooltip--anim-bounce { animation: _osl_tip_bounce 0.40s cubic-bezier(0.34,1.56,0.64,1) forwards !important; }
4315
- .osl-tooltip--anim-fade { animation: _osl_tip_fade 0.22s ease-out forwards !important; }
4316
-
4317
- @keyframes _osl_tip_up { from { opacity:0; transform:translateY(8px); } to { opacity:1; transform:translateY(0); } }
4318
- @keyframes _osl_tip_down { from { opacity:0; transform:translateY(-8px); } to { opacity:1; transform:translateY(0); } }
4319
- @keyframes _osl_tip_left { from { opacity:0; transform:translateX(8px); } to { opacity:1; transform:translateX(0); } }
4320
- @keyframes _osl_tip_right { from { opacity:0; transform:translateX(-8px); } to { opacity:1; transform:translateX(0); } }
4321
- @keyframes _osl_tip_scale {
4322
- from { opacity:0; transform:scale(0.72); }
4323
- to { opacity:1; transform:scale(1); }
4324
- }
4325
- @keyframes _osl_tip_bounce {
4326
- 0% { opacity:0; transform:scale(0.55); }
4327
- 55% { opacity:1; transform:scale(1.12); }
4328
- 75% { transform:scale(0.95); }
4329
- 90% { transform:scale(1.03); }
4330
- 100% { transform:scale(1); }
4331
- }
4332
- @keyframes _osl_tip_fade { from { opacity:0; } to { opacity:1; } }
4333
- `;
4334
- class OslTooltipDirective {
4335
- el;
4336
- renderer;
4337
- document;
4338
- text = '';
4339
- oslTooltipPosition = 'top';
4340
- oslTooltipAnimation = 'slide';
4341
- oslTooltipDisabled = false;
4342
- oslTooltipMaxWidth = '280px';
4343
- tooltipEl = null;
4344
- static cssInjected = false;
4345
- constructor(el, renderer, document) {
4346
- this.el = el;
4347
- this.renderer = renderer;
4348
- this.document = document;
4349
- this.injectCss();
4350
- }
4351
- show() {
4352
- if (this.oslTooltipDisabled || !this.text?.trim())
4353
- return;
4354
- this.create();
4355
- this.position();
4356
- }
4357
- hide() {
4358
- this.destroy();
4359
- }
4360
- create() {
4361
- this.destroy();
4362
- const tip = this.renderer.createElement('div');
4363
- this.renderer.addClass(tip, 'osl-tooltip');
4364
- this.renderer.addClass(tip, `osl-tooltip--${this.oslTooltipPosition}`);
4365
- if (this.oslTooltipAnimation !== 'slide') {
4366
- this.renderer.addClass(tip, `osl-tooltip--anim-${this.oslTooltipAnimation}`);
4367
- }
4368
- this.renderer.setStyle(tip, 'max-width', this.oslTooltipMaxWidth);
4369
- this.renderer.appendChild(tip, this.renderer.createText(this.text));
4370
- this.renderer.appendChild(this.document.body, tip);
4371
- this.tooltipEl = tip;
4372
- }
4373
- position() {
4374
- if (!this.tooltipEl)
4375
- return;
4376
- const host = this.el.nativeElement.getBoundingClientRect();
4377
- const tip = this.tooltipEl.getBoundingClientRect();
4378
- const gap = 8;
4379
- let top;
4380
- let left;
4381
- switch (this.oslTooltipPosition) {
4382
- case 'bottom':
4383
- top = host.bottom + gap;
4384
- left = host.left + host.width / 2 - tip.width / 2;
4385
- break;
4386
- case 'left':
4387
- top = host.top + host.height / 2 - tip.height / 2;
4388
- left = host.left - tip.width - gap;
4389
- break;
4390
- case 'right':
4391
- top = host.top + host.height / 2 - tip.height / 2;
4392
- left = host.right + gap;
4393
- break;
4394
- default: // top
4395
- top = host.top - tip.height - gap;
4396
- left = host.left + host.width / 2 - tip.width / 2;
4397
- }
4398
- left = Math.max(8, Math.min(left, this.document.defaultView.innerWidth - tip.width - 8));
4399
- top = Math.max(8, top);
4400
- this.renderer.setStyle(this.tooltipEl, 'top', `${top}px`);
4401
- this.renderer.setStyle(this.tooltipEl, 'left', `${left}px`);
4402
- }
4403
- destroy() {
4404
- if (this.tooltipEl) {
4405
- if (this.document.body.contains(this.tooltipEl)) {
4406
- this.renderer.removeChild(this.document.body, this.tooltipEl);
4407
- }
4408
- this.tooltipEl = null;
4409
- }
4410
- }
4411
- injectCss() {
4412
- if (OslTooltipDirective.cssInjected || this.document.getElementById('_osl_tooltip_css')) {
4413
- OslTooltipDirective.cssInjected = true;
4414
- return;
4415
- }
4416
- const style = this.document.createElement('style');
4417
- style.id = '_osl_tooltip_css';
4418
- style.textContent = TOOLTIP_CSS;
4419
- this.document.head.appendChild(style);
4420
- OslTooltipDirective.cssInjected = true;
4421
- }
4422
- ngOnDestroy() {
4423
- this.destroy();
4424
- }
4425
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: OslTooltipDirective, deps: [{ token: i0.ElementRef }, { token: i0.Renderer2 }, { token: DOCUMENT }], target: i0.ɵɵFactoryTarget.Directive });
4426
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: OslTooltipDirective, isStandalone: true, selector: "[oslTooltip]", inputs: { text: ["oslTooltip", "text"], oslTooltipPosition: "oslTooltipPosition", oslTooltipAnimation: "oslTooltipAnimation", oslTooltipDisabled: "oslTooltipDisabled", oslTooltipMaxWidth: "oslTooltipMaxWidth" }, host: { listeners: { "mouseenter": "show()", "mouseleave": "hide()" } }, ngImport: i0 });
4427
- }
4428
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: OslTooltipDirective, decorators: [{
4429
- type: Directive,
4430
- args: [{ selector: '[oslTooltip]', standalone: true }]
4431
- }], ctorParameters: () => [{ type: i0.ElementRef }, { type: i0.Renderer2 }, { type: Document, decorators: [{
4432
- type: Inject,
4433
- args: [DOCUMENT]
4434
- }] }], propDecorators: { text: [{
4435
- type: Input,
4436
- args: ['oslTooltip']
4437
- }], oslTooltipPosition: [{
4438
- type: Input
4439
- }], oslTooltipAnimation: [{
4440
- type: Input
4441
- }], oslTooltipDisabled: [{
4442
- type: Input
4443
- }], oslTooltipMaxWidth: [{
4444
- type: Input
4445
- }], show: [{
4446
- type: HostListener,
4447
- args: ['mouseenter']
4448
- }], hide: [{
4449
- type: HostListener,
4450
- args: ['mouseleave']
4451
- }] } });
4452
-
4453
4364
  // ─── Component ────────────────────────────────────────────────────────────────
4454
4365
  class OslReportGrid {
4455
4366
  // Inputs
@@ -6062,7 +5973,6 @@ class FormStructureModule {
6062
5973
  OslTooltipDirective,
6063
5974
  MatDatepickerModule,
6064
5975
  MatMenuModule,
6065
- ScrollingModule,
6066
5976
  DragDropModule,
6067
5977
  MatTooltipModule,
6068
5978
  OverlayModule,
@@ -6094,7 +6004,6 @@ class FormStructureModule {
6094
6004
  OslSkeletonModule,
6095
6005
  MatDatepickerModule,
6096
6006
  MatMenuModule,
6097
- ScrollingModule,
6098
6007
  DragDropModule,
6099
6008
  MatTooltipModule,
6100
6009
  OverlayModule,
@@ -6148,7 +6057,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
6148
6057
  OslTooltipDirective,
6149
6058
  MatDatepickerModule,
6150
6059
  MatMenuModule,
6151
- ScrollingModule,
6152
6060
  DragDropModule,
6153
6061
  MatTooltipModule,
6154
6062
  OverlayModule,