osl-base-extended 6.10.0 → 7.1.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.
|
@@ -26,7 +26,6 @@ import { NgxMatDatetimepicker, NgxMatDatepickerInput } from '@ngxmc/datetime-pic
|
|
|
26
26
|
import { MatInputModule } from '@angular/material/input';
|
|
27
27
|
import { debounceTime as debounceTime$1, distinctUntilChanged as distinctUntilChanged$1, switchMap, tap } from 'rxjs/operators';
|
|
28
28
|
import { MatMenuModule } from '@angular/material/menu';
|
|
29
|
-
import { ScrollingModule } from '@angular/cdk/scrolling';
|
|
30
29
|
import * as i3$1 from '@angular/cdk/drag-drop';
|
|
31
30
|
import { moveItemInArray, DragDropModule } from '@angular/cdk/drag-drop';
|
|
32
31
|
import { TemplatePortal, PortalModule } from '@angular/cdk/portal';
|
|
@@ -3062,6 +3061,201 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
3062
3061
|
args: [{ providedIn: 'root' }]
|
|
3063
3062
|
}] });
|
|
3064
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
|
+
|
|
3065
3259
|
class OslSearchbar {
|
|
3066
3260
|
label = "Type to Search...";
|
|
3067
3261
|
onSearch = new EventEmitter();
|
|
@@ -3346,7 +3540,6 @@ class OslSetup {
|
|
|
3346
3540
|
customFooterWrapperTpl;
|
|
3347
3541
|
searchbar;
|
|
3348
3542
|
gridRef;
|
|
3349
|
-
cardContainerRef;
|
|
3350
3543
|
// ── Inputs ────────────────────────────────────────────────────
|
|
3351
3544
|
title = '';
|
|
3352
3545
|
columns = [];
|
|
@@ -3372,10 +3565,12 @@ class OslSetup {
|
|
|
3372
3565
|
stateKey = '';
|
|
3373
3566
|
primaryKey = 'id';
|
|
3374
3567
|
onSave;
|
|
3375
|
-
/** Fixed page size used for card view
|
|
3568
|
+
/** Fixed page size used for card view pagination. Defaults to pageSize. */
|
|
3376
3569
|
cardPageSize;
|
|
3377
3570
|
/** Optional custom card template. Context: { $implicit: row, index: number } */
|
|
3378
3571
|
cardTemplate;
|
|
3572
|
+
/** Bootstrap col-* class number for each field in the card body. Default: 3 (4 per row). */
|
|
3573
|
+
cardCol = 3;
|
|
3379
3574
|
// ── Outputs ───────────────────────────────────────────────────
|
|
3380
3575
|
onSearch = new EventEmitter();
|
|
3381
3576
|
onAdd = new EventEmitter();
|
|
@@ -3393,14 +3588,10 @@ class OslSetup {
|
|
|
3393
3588
|
// ── View mode ─────────────────────────────────────────────────
|
|
3394
3589
|
viewMode = 'table';
|
|
3395
3590
|
// ── Card view state ───────────────────────────────────────────
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
allCardsLoaded = false;
|
|
3591
|
+
cardCurrentPage = 1;
|
|
3592
|
+
cardPageSizeOptions = [10, 25, 50, 100];
|
|
3399
3593
|
cardOpenMenuIndex = null;
|
|
3400
3594
|
cardMenuPosition = { top: 0, left: 0 };
|
|
3401
|
-
_cardExpectedPage = 0;
|
|
3402
|
-
_cardRestoreTargetPage = 0;
|
|
3403
|
-
_needsInitialCardLoad = false;
|
|
3404
3595
|
onDocumentClick() {
|
|
3405
3596
|
this.cardOpenMenuIndex = null;
|
|
3406
3597
|
}
|
|
@@ -3422,12 +3613,57 @@ class OslSetup {
|
|
|
3422
3613
|
get skeletonCardRows() {
|
|
3423
3614
|
return Array.from({ length: 6 });
|
|
3424
3615
|
}
|
|
3616
|
+
// ── Card pagination ───────────────────────────────────────────
|
|
3617
|
+
get _cardTotal() {
|
|
3618
|
+
return this.autoMode ? this.datasource.length : this.totalRecords;
|
|
3619
|
+
}
|
|
3620
|
+
get cardTotalPages() {
|
|
3621
|
+
return Math.ceil(this._cardTotal / this._effectiveCardPageSize) || 1;
|
|
3622
|
+
}
|
|
3623
|
+
get cardPagedData() {
|
|
3624
|
+
if (!this.autoMode)
|
|
3625
|
+
return this.datasource;
|
|
3626
|
+
const ps = this._effectiveCardPageSize;
|
|
3627
|
+
return this.datasource.slice((this.cardCurrentPage - 1) * ps, this.cardCurrentPage * ps);
|
|
3628
|
+
}
|
|
3629
|
+
get cardPageNumbers() {
|
|
3630
|
+
const total = this.cardTotalPages;
|
|
3631
|
+
if (total <= 7)
|
|
3632
|
+
return Array.from({ length: total }, (_, i) => i + 1);
|
|
3633
|
+
const pages = [1];
|
|
3634
|
+
if (this.cardCurrentPage > 3)
|
|
3635
|
+
pages.push(-1);
|
|
3636
|
+
for (let i = Math.max(2, this.cardCurrentPage - 1); i <= Math.min(total - 1, this.cardCurrentPage + 1); i++) {
|
|
3637
|
+
pages.push(i);
|
|
3638
|
+
}
|
|
3639
|
+
if (this.cardCurrentPage < total - 2)
|
|
3640
|
+
pages.push(-1);
|
|
3641
|
+
pages.push(total);
|
|
3642
|
+
return pages;
|
|
3643
|
+
}
|
|
3644
|
+
get cardStartRecord() {
|
|
3645
|
+
if (this._cardTotal === 0)
|
|
3646
|
+
return 0;
|
|
3647
|
+
return (this.cardCurrentPage - 1) * this._effectiveCardPageSize + 1;
|
|
3648
|
+
}
|
|
3649
|
+
get cardEndRecord() {
|
|
3650
|
+
return Math.min(this.cardCurrentPage * this._effectiveCardPageSize, this._cardTotal);
|
|
3651
|
+
}
|
|
3652
|
+
cardGoToPage(page) {
|
|
3653
|
+
if (page < 1 || page > this.cardTotalPages)
|
|
3654
|
+
return;
|
|
3655
|
+
this.cardCurrentPage = page;
|
|
3656
|
+
if (!this.autoMode) {
|
|
3657
|
+
this.pageChange.emit({ page, pageSize: this._effectiveCardPageSize, searchValue: this.searchbar?.searchControl?.value ?? '' });
|
|
3658
|
+
}
|
|
3659
|
+
}
|
|
3660
|
+
cardOnPageSizeChange(size) {
|
|
3661
|
+
this.cardCurrentPage = 1;
|
|
3662
|
+
this.pageSizeChange.emit({ page: 1, pageSize: Number(size), searchValue: this.searchbar?.searchControl?.value ?? '' });
|
|
3663
|
+
}
|
|
3425
3664
|
// ── Lifecycle ─────────────────────────────────────────────────
|
|
3426
3665
|
ngOnInit() {
|
|
3427
3666
|
this._loadViewMode();
|
|
3428
|
-
if (this.viewMode === 'card') {
|
|
3429
|
-
this._needsInitialCardLoad = true;
|
|
3430
|
-
}
|
|
3431
3667
|
const route = this._injector.get(ActivatedRoute, null);
|
|
3432
3668
|
if (route) {
|
|
3433
3669
|
const id = route.snapshot.queryParamMap.get('id');
|
|
@@ -3437,10 +3673,6 @@ class OslSetup {
|
|
|
3437
3673
|
}
|
|
3438
3674
|
ngAfterViewInit() {
|
|
3439
3675
|
this.statemainTain();
|
|
3440
|
-
if (this._needsInitialCardLoad) {
|
|
3441
|
-
this._needsInitialCardLoad = false;
|
|
3442
|
-
setTimeout(() => { this._startCardLoad(); });
|
|
3443
|
-
}
|
|
3444
3676
|
if (this._pendingAutoEditId !== null) {
|
|
3445
3677
|
const id = this._pendingAutoEditId;
|
|
3446
3678
|
this._pendingAutoEditId = null;
|
|
@@ -3454,55 +3686,10 @@ class OslSetup {
|
|
|
3454
3686
|
}
|
|
3455
3687
|
ngOnChanges(changes) {
|
|
3456
3688
|
if (changes['datasource']) {
|
|
3457
|
-
if (this.viewMode === 'card') {
|
|
3458
|
-
|
|
3459
|
-
// Local pagination: datasource changed (search filtered or initial load), reset
|
|
3460
|
-
if (!changes['datasource'].firstChange) {
|
|
3461
|
-
this._loadCardPageLocally(1);
|
|
3462
|
-
}
|
|
3463
|
-
}
|
|
3464
|
-
else if (this._cardExpectedPage !== 0) {
|
|
3465
|
-
const newData = changes['datasource'].currentValue ?? [];
|
|
3466
|
-
const isFirstPage = this._cardExpectedPage === 1;
|
|
3467
|
-
this._cardExpectedPage = 0;
|
|
3468
|
-
if (isFirstPage) {
|
|
3469
|
-
this.cardDatasource = [...newData];
|
|
3470
|
-
this.cardPage = 1;
|
|
3471
|
-
this.allCardsLoaded = false;
|
|
3472
|
-
}
|
|
3473
|
-
else if (newData.length > 0) {
|
|
3474
|
-
this.cardDatasource = [...this.cardDatasource, ...newData];
|
|
3475
|
-
this.cardPage++;
|
|
3476
|
-
}
|
|
3477
|
-
if (newData.length === 0 || (this.totalRecords > 0 && this.cardDatasource.length >= this.totalRecords)) {
|
|
3478
|
-
this.allCardsLoaded = true;
|
|
3479
|
-
}
|
|
3480
|
-
// Multi-page state restore: continue loading until target page is reached
|
|
3481
|
-
if (this._cardRestoreTargetPage > 0 && this.cardPage < this._cardRestoreTargetPage && !this.allCardsLoaded) {
|
|
3482
|
-
const nextPage = this.cardPage + 1;
|
|
3483
|
-
this._cardExpectedPage = nextPage;
|
|
3484
|
-
this.pageChange.emit({ page: nextPage, pageSize: this._effectiveCardPageSize, searchValue: this.searchbar?.searchControl?.value ?? '' });
|
|
3485
|
-
}
|
|
3486
|
-
else if (this._cardRestoreTargetPage > 0 && (this.cardPage >= this._cardRestoreTargetPage || this.allCardsLoaded)) {
|
|
3487
|
-
this._cardRestoreTargetPage = 0;
|
|
3488
|
-
if (this._pendingScrollTop !== null) {
|
|
3489
|
-
const top = this._pendingScrollTop;
|
|
3490
|
-
this._pendingScrollTop = null;
|
|
3491
|
-
setTimeout(() => { this.cardContainerRef?.nativeElement?.scrollTo({ top }); }, 50);
|
|
3492
|
-
}
|
|
3493
|
-
}
|
|
3494
|
-
}
|
|
3495
|
-
else if (!changes['datasource'].firstChange) {
|
|
3496
|
-
// External refresh (e.g. after approve/delete via more-actions) — replace cards with fresh data
|
|
3497
|
-
const newData = changes['datasource'].currentValue ?? [];
|
|
3498
|
-
this.cardDatasource = [...newData];
|
|
3499
|
-
this.cardPage = 1;
|
|
3500
|
-
this.allCardsLoaded = newData.length === 0
|
|
3501
|
-
|| (this.totalRecords > 0 && newData.length >= this.totalRecords)
|
|
3502
|
-
|| newData.length < this._effectiveCardPageSize;
|
|
3503
|
-
}
|
|
3689
|
+
if (this.viewMode === 'card' && this.autoMode && !changes['datasource'].firstChange) {
|
|
3690
|
+
this.cardCurrentPage = 1;
|
|
3504
3691
|
}
|
|
3505
|
-
else if (this._pendingScrollTop !== null) {
|
|
3692
|
+
else if (this.viewMode === 'table' && this._pendingScrollTop !== null) {
|
|
3506
3693
|
const ds = changes['datasource'].currentValue;
|
|
3507
3694
|
if (ds?.length > 0) {
|
|
3508
3695
|
const top = this._pendingScrollTop;
|
|
@@ -3533,77 +3720,18 @@ class OslSetup {
|
|
|
3533
3720
|
if (key)
|
|
3534
3721
|
localStorage.setItem(key, mode);
|
|
3535
3722
|
if (mode === 'card') {
|
|
3536
|
-
this.
|
|
3723
|
+
this.cardCurrentPage = 1;
|
|
3724
|
+
if (!this.autoMode) {
|
|
3725
|
+
this.pageChange.emit({ page: 1, pageSize: this._effectiveCardPageSize, searchValue: this.searchbar?.searchControl?.value ?? '' });
|
|
3726
|
+
}
|
|
3537
3727
|
}
|
|
3538
3728
|
else {
|
|
3539
|
-
this._cardExpectedPage = 0;
|
|
3540
3729
|
if (this.gridRef)
|
|
3541
3730
|
this.gridRef.currentPage = 1;
|
|
3542
3731
|
this.pageChange.emit({ page: 1, pageSize: this.pageSize, searchValue: this.searchbar?.searchControl?.value ?? '' });
|
|
3543
3732
|
}
|
|
3544
3733
|
}
|
|
3545
|
-
// ── Card
|
|
3546
|
-
_startCardLoad() {
|
|
3547
|
-
this.cardDatasource = [];
|
|
3548
|
-
this.cardPage = 0;
|
|
3549
|
-
this.allCardsLoaded = false;
|
|
3550
|
-
this._cardRestoreTargetPage = 0;
|
|
3551
|
-
if (this.autoMode) {
|
|
3552
|
-
this._loadCardPageLocally(1);
|
|
3553
|
-
}
|
|
3554
|
-
else {
|
|
3555
|
-
this._cardExpectedPage = 1;
|
|
3556
|
-
this.pageChange.emit({ page: 1, pageSize: this._effectiveCardPageSize, searchValue: this.searchbar?.searchControl?.value ?? '' });
|
|
3557
|
-
}
|
|
3558
|
-
}
|
|
3559
|
-
_loadCardPageLocally(page) {
|
|
3560
|
-
const ps = this._effectiveCardPageSize;
|
|
3561
|
-
const slice = this.datasource.slice((page - 1) * ps, page * ps);
|
|
3562
|
-
if (page === 1) {
|
|
3563
|
-
this.cardDatasource = [...slice];
|
|
3564
|
-
this.allCardsLoaded = false;
|
|
3565
|
-
}
|
|
3566
|
-
else {
|
|
3567
|
-
this.cardDatasource = [...this.cardDatasource, ...slice];
|
|
3568
|
-
}
|
|
3569
|
-
this.cardPage = page;
|
|
3570
|
-
if (this.datasource.length > 0 && (this.cardDatasource.length >= this.datasource.length || slice.length < ps)) {
|
|
3571
|
-
this.allCardsLoaded = true;
|
|
3572
|
-
}
|
|
3573
|
-
}
|
|
3574
|
-
loadMoreCards() {
|
|
3575
|
-
if (this.loading || this.allCardsLoaded)
|
|
3576
|
-
return;
|
|
3577
|
-
if (this.autoMode) {
|
|
3578
|
-
if (this.cardDatasource.length >= this.datasource.length) {
|
|
3579
|
-
this.allCardsLoaded = true;
|
|
3580
|
-
return;
|
|
3581
|
-
}
|
|
3582
|
-
this._loadCardPageLocally(this.cardPage + 1);
|
|
3583
|
-
return;
|
|
3584
|
-
}
|
|
3585
|
-
if (this._cardExpectedPage !== 0)
|
|
3586
|
-
return;
|
|
3587
|
-
if (this.totalRecords > 0 && this.cardDatasource.length >= this.totalRecords) {
|
|
3588
|
-
this.allCardsLoaded = true;
|
|
3589
|
-
return;
|
|
3590
|
-
}
|
|
3591
|
-
const nextPage = this.cardPage + 1;
|
|
3592
|
-
this._cardExpectedPage = nextPage;
|
|
3593
|
-
this.pageChange.emit({ page: nextPage, pageSize: this._effectiveCardPageSize, searchValue: this.searchbar?.searchControl?.value ?? '' });
|
|
3594
|
-
}
|
|
3595
|
-
onCardScroll(event) {
|
|
3596
|
-
const el = event.target;
|
|
3597
|
-
if (el.scrollHeight - el.scrollTop - el.clientHeight < 150) {
|
|
3598
|
-
this.loadMoreCards();
|
|
3599
|
-
}
|
|
3600
|
-
}
|
|
3601
|
-
isHighlightedCard(row) {
|
|
3602
|
-
if (!this.restoredRow || !this.primaryKey)
|
|
3603
|
-
return false;
|
|
3604
|
-
const val = row[this.primaryKey];
|
|
3605
|
-
return val !== undefined && val === this.restoredRow[this.primaryKey];
|
|
3606
|
-
}
|
|
3734
|
+
// ── Card helpers ──────────────────────────────────────────────
|
|
3607
3735
|
toggleCardMenu(index, event) {
|
|
3608
3736
|
event.stopPropagation();
|
|
3609
3737
|
if (this.cardOpenMenuIndex === index) {
|
|
@@ -3616,6 +3744,12 @@ class OslSetup {
|
|
|
3616
3744
|
this.cardMenuPosition = { top: rect.bottom + 6, left };
|
|
3617
3745
|
this.cardOpenMenuIndex = index;
|
|
3618
3746
|
}
|
|
3747
|
+
isHighlightedCard(row) {
|
|
3748
|
+
if (!this.restoredRow || !this.primaryKey)
|
|
3749
|
+
return false;
|
|
3750
|
+
const val = row[this.primaryKey];
|
|
3751
|
+
return val !== undefined && val === this.restoredRow[this.primaryKey];
|
|
3752
|
+
}
|
|
3619
3753
|
getCellValue(row, col) {
|
|
3620
3754
|
const raw = row[col.key];
|
|
3621
3755
|
if (col.displayFn)
|
|
@@ -3642,43 +3776,23 @@ class OslSetup {
|
|
|
3642
3776
|
const state = this._stateService.consume(this.stateKey);
|
|
3643
3777
|
if (!state)
|
|
3644
3778
|
return;
|
|
3645
|
-
this._pendingScrollTop = state.scrollTop;
|
|
3646
3779
|
this.restoredRow = state.highlightedRow;
|
|
3647
3780
|
if (this.viewMode === 'card') {
|
|
3648
|
-
this.
|
|
3649
|
-
this.
|
|
3650
|
-
|
|
3651
|
-
|
|
3652
|
-
if (this.autoMode) {
|
|
3653
|
-
// Restore pages locally from current datasource
|
|
3654
|
-
const targetCount = state.page * this._effectiveCardPageSize;
|
|
3655
|
-
this.cardDatasource = this.datasource.slice(0, targetCount);
|
|
3656
|
-
this.cardPage = state.page;
|
|
3657
|
-
this.allCardsLoaded = this.cardDatasource.length >= this.datasource.length;
|
|
3658
|
-
if (this._pendingScrollTop !== null) {
|
|
3659
|
-
const top = this._pendingScrollTop;
|
|
3660
|
-
this._pendingScrollTop = null;
|
|
3661
|
-
setTimeout(() => { this.cardContainerRef?.nativeElement?.scrollTo({ top }); }, 50);
|
|
3662
|
-
}
|
|
3663
|
-
this.onStateRestored.emit(state);
|
|
3781
|
+
this.cardCurrentPage = state.page;
|
|
3782
|
+
if (this.searchbar && state.searchValue) {
|
|
3783
|
+
this._isRestoring = true;
|
|
3784
|
+
this.searchbar.searchControl.setValue(state.searchValue, { emitEvent: false });
|
|
3664
3785
|
}
|
|
3665
|
-
|
|
3666
|
-
this.
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
|
|
3670
|
-
}
|
|
3671
|
-
this._cardExpectedPage = 1;
|
|
3672
|
-
if (state.searchValue) {
|
|
3673
|
-
this.onSearchSetup(state.searchValue);
|
|
3674
|
-
}
|
|
3675
|
-
else {
|
|
3676
|
-
this.pageChange.emit({ page: 1, pageSize: this._effectiveCardPageSize, searchValue: '' });
|
|
3677
|
-
}
|
|
3678
|
-
this.onStateRestored.emit(state);
|
|
3786
|
+
if (state.searchValue) {
|
|
3787
|
+
this.onSearchSetup(state.searchValue);
|
|
3788
|
+
}
|
|
3789
|
+
else if (!this.autoMode) {
|
|
3790
|
+
this.pageChange.emit({ page: state.page, pageSize: state.pageSize, searchValue: '' });
|
|
3679
3791
|
}
|
|
3792
|
+
this.onStateRestored.emit(state);
|
|
3680
3793
|
}
|
|
3681
3794
|
else {
|
|
3795
|
+
this._pendingScrollTop = state.scrollTop;
|
|
3682
3796
|
if (this.gridRef) {
|
|
3683
3797
|
this.gridRef.currentPage = state.page;
|
|
3684
3798
|
this.gridRef.pageSize = state.pageSize;
|
|
@@ -3704,18 +3818,12 @@ class OslSetup {
|
|
|
3704
3818
|
// ── Search ────────────────────────────────────────────────────
|
|
3705
3819
|
onSearchSetup(event) {
|
|
3706
3820
|
if (this.viewMode === 'card' && this.autoMode) {
|
|
3707
|
-
|
|
3821
|
+
this.cardCurrentPage = 1;
|
|
3708
3822
|
this.onSearch.emit(event);
|
|
3709
3823
|
return;
|
|
3710
3824
|
}
|
|
3711
|
-
if (this.viewMode === 'card') {
|
|
3712
|
-
this.cardDatasource = [];
|
|
3713
|
-
this.cardPage = 0;
|
|
3714
|
-
this.allCardsLoaded = false;
|
|
3715
|
-
this._cardExpectedPage = 1;
|
|
3716
|
-
}
|
|
3717
3825
|
if (this._isRestoring) {
|
|
3718
|
-
const restoredPage = this.viewMode === 'card' ?
|
|
3826
|
+
const restoredPage = this.viewMode === 'card' ? this.cardCurrentPage : (this.gridRef?.currentPage ?? 1);
|
|
3719
3827
|
this._isRestoring = false;
|
|
3720
3828
|
this.pageChange.emit({
|
|
3721
3829
|
page: restoredPage,
|
|
@@ -3725,6 +3833,9 @@ class OslSetup {
|
|
|
3725
3833
|
this.onSearch.emit(event);
|
|
3726
3834
|
return;
|
|
3727
3835
|
}
|
|
3836
|
+
if (this.viewMode === 'card') {
|
|
3837
|
+
this.cardCurrentPage = 1;
|
|
3838
|
+
}
|
|
3728
3839
|
if (this.gridRef) {
|
|
3729
3840
|
this.gridRef.clearRestorePage();
|
|
3730
3841
|
this.gridRef.currentPage = 1;
|
|
@@ -3734,7 +3845,7 @@ class OslSetup {
|
|
|
3734
3845
|
pageSize: this.viewMode === 'card' ? this._effectiveCardPageSize : (this.gridRef?.pageSize || 10),
|
|
3735
3846
|
searchValue: event,
|
|
3736
3847
|
sortASC: this.gridRef?.sortAsc,
|
|
3737
|
-
sortKey: this.gridRef?.sortKey
|
|
3848
|
+
sortKey: this.gridRef?.sortKey,
|
|
3738
3849
|
});
|
|
3739
3850
|
this.onSearch.emit(event);
|
|
3740
3851
|
}
|
|
@@ -3752,7 +3863,7 @@ class OslSetup {
|
|
|
3752
3863
|
eventEmitter.emit({ ...event, ...{
|
|
3753
3864
|
searchValue: this.searchbar?.searchControl?.value,
|
|
3754
3865
|
sortKey: this.gridRef?.sortKey,
|
|
3755
|
-
sortASC: this.gridRef?.sortAsc
|
|
3866
|
+
sortASC: this.gridRef?.sortAsc,
|
|
3756
3867
|
} });
|
|
3757
3868
|
}
|
|
3758
3869
|
// ── Dialog actions ────────────────────────────────────────────
|
|
@@ -3775,12 +3886,10 @@ class OslSetup {
|
|
|
3775
3886
|
this.dialogMode = 'edit';
|
|
3776
3887
|
if (this.stateKey) {
|
|
3777
3888
|
this._stateService.save(this.stateKey, {
|
|
3778
|
-
page: this.viewMode === 'card' ? this.
|
|
3889
|
+
page: this.viewMode === 'card' ? this.cardCurrentPage : (this.gridRef?.currentPage ?? 1),
|
|
3779
3890
|
pageSize: this.viewMode === 'card' ? this._effectiveCardPageSize : (this.gridRef?.pageSize ?? this.pageSize),
|
|
3780
3891
|
searchValue: this.searchbar?.searchControl?.value ?? '',
|
|
3781
|
-
scrollTop: this.viewMode === 'card'
|
|
3782
|
-
? (this.cardContainerRef?.nativeElement?.scrollTop ?? 0)
|
|
3783
|
-
: (this.gridRef?.getScrollTop() ?? 0),
|
|
3892
|
+
scrollTop: this.viewMode === 'card' ? 0 : (this.gridRef?.getScrollTop() ?? 0),
|
|
3784
3893
|
highlightedRow: row,
|
|
3785
3894
|
});
|
|
3786
3895
|
}
|
|
@@ -3847,11 +3956,11 @@ class OslSetup {
|
|
|
3847
3956
|
});
|
|
3848
3957
|
}
|
|
3849
3958
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: OslSetup, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3850
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: OslSetup, isStandalone: false, selector: "osl-setup", inputs: { title: "title", columns: "columns", datasource: "datasource", isPaginated: "isPaginated", pageSize: "pageSize", autoMode: "autoMode", tableHeight: "tableHeight", totalRecords: "totalRecords", loading: "loading", dialogWidth: "dialogWidth", formElements: "formElements", beforeDisplay: "beforeDisplay", onAddEditFn: "onAddEditFn", isLister: "isLister", canAdd: "canAdd", canEdit: "canEdit", canDelete: "canDelete", moreMenuActions: "moreMenuActions", customFormFooter: "customFormFooter", customHeaderTemp: "customHeaderTemp", partialCustomHeaderTemp: "partialCustomHeaderTemp", stateKey: "stateKey", primaryKey: "primaryKey", onSave: "onSave", cardPageSize: "cardPageSize", cardTemplate: "cardTemplate" }, outputs: { onSearch: "onSearch", onAdd: "onAdd", onEdit: "onEdit", onDelete: "onDelete", pageChange: "pageChange", pageSizeChange: "pageSizeChange", sortChange: "sortChange", onRowClick: "onRowClick", onStateRestored: "onStateRestored" }, host: { listeners: { "document:click": "onDocumentClick()" } }, viewQueries: [{ propertyName: "formBodyTpl", first: true, predicate: ["formBodyTpl"], descendants: true }, { propertyName: "formFooterTpl", first: true, predicate: ["formFooterTpl"], descendants: true }, { propertyName: "customFooterWrapperTpl", first: true, predicate: ["customFooterWrapperTpl"], descendants: true }, { propertyName: "searchbar", first: true, predicate: ["searchbar"], descendants: true }, { propertyName: "gridRef", first: true, predicate: ["gridRef"], descendants: true }, { propertyName: "cardContainerRef", first: true, predicate: ["cardContainerRef"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"p-2\">\n\n <!-- \u2500\u2500 Header \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div class=\"osl-setup-header\">\n <h5 class=\"mb-0\">{{ title }}</h5>\n <div class=\"d-flex align-items-center gap-2\">\n <osl-searchbar #searchbar class=\"mx-2\" (onSearch)=\"onSearchSetup($event)\"></osl-searchbar>\n\n <!-- View Toggle -->\n @if(!isLister){\n <div class=\"osl-view-toggle\">\n <button class=\"osl-view-toggle-btn\" [class.osl-view-toggle-btn--active]=\"viewMode === 'table'\"\n (click)=\"toggleView('table')\" title=\"Table view\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"2\"/>\n <line x1=\"3\" y1=\"9\" x2=\"21\" y2=\"9\"/>\n <line x1=\"3\" y1=\"15\" x2=\"21\" y2=\"15\"/>\n <line x1=\"9\" y1=\"9\" x2=\"9\" y2=\"21\"/>\n </svg>\n </button>\n <button class=\"osl-view-toggle-btn\" [class.osl-view-toggle-btn--active]=\"viewMode === 'card'\"\n (click)=\"toggleView('card')\" title=\"Card view\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <rect x=\"3\" y=\"3\" width=\"7\" height=\"7\" rx=\"1.5\"/>\n <rect x=\"14\" y=\"3\" width=\"7\" height=\"7\" rx=\"1.5\"/>\n <rect x=\"3\" y=\"14\" width=\"7\" height=\"7\" rx=\"1.5\"/>\n <rect x=\"14\" y=\"14\" width=\"7\" height=\"7\" rx=\"1.5\"/>\n </svg>\n </button>\n </div>\n }\n\n @if (!isLister && canAdd) {\n <osl-button variant=\"secondary\" size=\"sm\" [label]=\"'Add ' + title\" (clickEv)=\"openAddDialog()\"></osl-button>\n }\n </div>\n </div>\n\n <!-- \u2500\u2500 Table View \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (viewMode === 'table') {\n <div class=\"osl-setup-body my-2\">\n <osl-grid\n #gridRef\n [columns]=\"columnsWithActions\"\n [(datasource)]=\"datasource\"\n [isPaginated]=\"isPaginated\"\n [pageSize]=\"pageSize\"\n [autoMode]=\"autoMode\"\n [tableHeight]=\"tableHeight\"\n [totalRecords]=\"totalRecords\"\n [loading]=\"loading\"\n [moreMenuActions]=\"moreMenuActions\"\n [canEdit]=\"canEdit\"\n [canDelete]=\"canDelete\"\n [highlightedRow]=\"restoredRow\"\n [primaryKey]=\"primaryKey\"\n (editClick)=\"openEditDialog($event)\"\n (deleteClick)=\"onDeleteClick($event)\"\n (pageChange)=\"onPageChange(pageChange, $event)\"\n (pageSizeChange)=\"onPageChange(pageSizeChange, $event)\"\n (sortChange)=\"sortChange.emit($event)\"\n [isSelectable]=\"isLister\"\n (onRowClick)=\"onRowClick.emit($event)\"\n />\n </div>\n }\n\n <!-- \u2500\u2500 Card View \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (viewMode === 'card') {\n <div #cardContainerRef class=\"osl-card-container my-2\" [style.height]=\"tableHeight\" (scroll)=\"onCardScroll($event)\">\n\n <!-- Skeleton cards on initial load -->\n @if (loading && cardDatasource.length === 0) {\n <div class=\"osl-card-grid\">\n @for (sk of skeletonCardRows; track $index) {\n <div class=\"osl-card osl-card--skeleton\">\n <div class=\"osl-card-inner\">\n <div class=\"osl-card-header\">\n <div class=\"osl-card-avatar osl-card-avatar--skeleton\"></div>\n <div style=\"flex:1\">\n <div class=\"osl-sk-line osl-sk-line--title\"></div>\n </div>\n </div>\n <div class=\"osl-card-divider\"></div>\n <div class=\"osl-card-body\">\n <div class=\"osl-card-field\"><div class=\"osl-sk-line osl-sk-line--label\"></div><div class=\"osl-sk-line\"></div></div>\n <div class=\"osl-card-field\"><div class=\"osl-sk-line osl-sk-line--label\"></div><div class=\"osl-sk-line osl-sk-line--short\"></div></div>\n <div class=\"osl-card-field\"><div class=\"osl-sk-line osl-sk-line--label\"></div><div class=\"osl-sk-line\"></div></div>\n </div>\n </div>\n </div>\n }\n </div>\n }\n\n <!-- Empty state -->\n @else if (cardDatasource.length === 0 && !loading) {\n <div class=\"osl-card-empty\">\n <svg class=\"osl-card-empty-svg\" viewBox=\"0 0 180 160\" fill=\"none\">\n <ellipse cx=\"90\" cy=\"140\" rx=\"60\" ry=\"8\" fill=\"#f3f4f6\"/>\n <rect x=\"30\" y=\"30\" width=\"120\" height=\"96\" rx=\"10\" fill=\"#f9fafb\" stroke=\"#e5e7eb\" stroke-width=\"2\"/>\n <rect x=\"44\" y=\"48\" width=\"92\" height=\"10\" rx=\"5\" fill=\"#e5e7eb\"/>\n <rect x=\"44\" y=\"66\" width=\"72\" height=\"8\" rx=\"4\" fill=\"#f3f4f6\"/>\n <rect x=\"44\" y=\"82\" width=\"84\" height=\"8\" rx=\"4\" fill=\"#f3f4f6\"/>\n <rect x=\"44\" y=\"98\" width=\"56\" height=\"8\" rx=\"4\" fill=\"#f3f4f6\"/>\n <circle cx=\"140\" cy=\"108\" r=\"26\" fill=\"#fff\" stroke=\"#e5e7eb\" stroke-width=\"2\"/>\n <line x1=\"133\" y1=\"101\" x2=\"147\" y2=\"115\" stroke=\"#d1d5db\" stroke-width=\"2.5\" stroke-linecap=\"round\"/>\n <line x1=\"147\" y1=\"101\" x2=\"133\" y2=\"115\" stroke=\"#d1d5db\" stroke-width=\"2.5\" stroke-linecap=\"round\"/>\n </svg>\n <p class=\"osl-card-empty-title\">No records found</p>\n <p class=\"osl-card-empty-desc\">Try adjusting your search or filter criteria</p>\n </div>\n }\n\n <!-- Card grid -->\n @else {\n <div class=\"osl-card-grid\">\n\n @if (cardTemplate) {\n @for (row of cardDatasource; track row[primaryKey]; let i = $index) {\n <ng-container *ngTemplateOutlet=\"cardTemplate; context: { $implicit: row, index: i }\"></ng-container>\n }\n } @else {\n @for (row of cardDatasource; track row[primaryKey]; let i = $index) {\n <div class=\"osl-card\"\n [class.osl-card--highlighted]=\"isHighlightedCard(row)\"\n [class.osl-card--selectable]=\"isLister\"\n (click)=\"isLister ? onRowClick.emit(row) : null\">\n\n <div class=\"osl-card-inner\">\n\n <!-- Card header -->\n <div class=\"osl-card-header\">\n <!-- Avatar circle -->\n @if (cardTitleColumn) {\n <div class=\"osl-card-avatar\">{{ getCardInitial(row) }}</div>\n }\n\n <!-- Title -->\n <div class=\"osl-card-title-wrap\">\n <span class=\"osl-card-title\">\n @if (cardTitleColumn) {\n @switch (cardTitleColumn.displayType) {\n @case ('date') { {{ row[cardTitleColumn.key] | date }} }\n @case ('datetime') { {{ row[cardTitleColumn.key] | date:'medium' }} }\n @case ('time') { {{ row[cardTitleColumn.key] | date:'shortTime' }} }\n @case ('customDateFormat') { {{ row[cardTitleColumn.key] | date:cardTitleColumn.customDateFormat }} }\n @default { {{ getCellValue(row, cardTitleColumn) }} }\n }\n }\n </span>\n </div>\n\n <!-- Action buttons -->\n @if (!isLister && ((hasForm && (canEdit || canDelete)) || (moreMenuActions.length > 0 && hasVisibleActions(row)))) {\n <div class=\"osl-card-actions\">\n\n <!-- More-actions trigger (menu rendered at root to avoid transform containment) -->\n @if (moreMenuActions.length > 0 && hasVisibleActions(row)) {\n <button class=\"osl-card-action-btn osl-card-action-btn--more\"\n (click)=\"toggleCardMenu(i, $event)\" title=\"More actions\">\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <circle cx=\"12\" cy=\"5\" r=\"2.2\"/>\n <circle cx=\"12\" cy=\"12\" r=\"2.2\"/>\n <circle cx=\"12\" cy=\"19\" r=\"2.2\"/>\n </svg>\n </button>\n }\n\n <!-- Edit -->\n @if (hasForm && canEdit) {\n <button class=\"osl-card-action-btn osl-card-action-btn--edit\"\n (click)=\"$event.stopPropagation(); openEditDialog(row)\" title=\"Edit\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7\"/>\n <path d=\"M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z\"/>\n </svg>\n </button>\n }\n\n <!-- Delete -->\n @if (hasForm && canDelete) {\n <button class=\"osl-card-action-btn osl-card-action-btn--delete\"\n (click)=\"$event.stopPropagation(); onDeleteClick(row)\" title=\"Delete\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polyline points=\"3 6 5 6 21 6\"/>\n <path d=\"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2\"/>\n </svg>\n </button>\n }\n </div>\n }\n </div>\n\n <!-- Divider -->\n <div class=\"osl-card-divider\"></div>\n\n <!-- Card body: remaining fields -->\n <div class=\"osl-card-body\">\n @for (col of cardBodyColumns; track col.key) {\n <div class=\"osl-card-field\">\n <span class=\"osl-card-label\">{{ col.label }}</span>\n <span class=\"osl-card-value\" [class.link]=\"col.displayType === 'link'\" (click)=\"col.click ? col.click(row, col) : null\">\n @switch (col.displayType) {\n @case ('date') { {{ row[col.key] | date }} }\n @case ('datetime') { {{ row[col.key] | date:'medium' }} }\n @case ('time') { {{ row[col.key] | date:'shortTime' }} }\n @case ('customDateFormat') { {{ row[col.key] | date:col.customDateFormat }} }\n\n @default { {{ getCellValue(row, col) }} }\n }\n </span>\n </div>\n }\n </div>\n\n </div>\n </div>\n }\n }\n </div>\n\n <!-- Infinite scroll bottom loader -->\n @if (loading) {\n <div class=\"osl-card-loader\">\n <div class=\"osl-card-loader-track\">\n <div class=\"osl-card-loader-bar\"></div>\n </div>\n <span class=\"osl-card-loader-text\">Loading more\u2026</span>\n </div>\n }\n\n <!-- All loaded footer -->\n @if (allCardsLoaded && !loading && cardDatasource.length > 0) {\n <div class=\"osl-card-all-loaded\">\n <span class=\"osl-card-all-loaded-line\"></span>\n <span class=\"osl-card-all-loaded-text\">All {{ totalRecords || cardDatasource.length }} records loaded</span>\n <span class=\"osl-card-all-loaded-line\"></span>\n </div>\n }\n }\n </div>\n }\n\n</div>\n\n<!-- Dialog: Form Body -->\n<ng-template #formBodyTpl>\n <osl-dynamic-form\n [skeletonLoading]=\"formLoading\"\n [elements]=\"formElements\"\n [(model)]=\"dialogModel\"\n ></osl-dynamic-form>\n</ng-template>\n\n<!-- Dialog: Form Footer -->\n<ng-template #formFooterTpl>\n <div class=\"osl-setup-dialog-footer\">\n <osl-button [loading]=\"saveLoading\" variant=\"secondary\" label=\"Save\" (click)=\"saveDialog()\"></osl-button>\n </div>\n</ng-template>\n\n<ng-template #customFooterWrapperTpl let-data>\n <ng-container *ngTemplateOutlet=\"customFormFooter!; context: { $implicit: { dialogModel: dialogModel, dialogMode: dialogMode, dialogRef: data.dialogRef } }\"></ng-container>\n</ng-template>\n\n<!-- Floating card more-actions menu \u2014 rendered outside card DOM to avoid transform containment -->\n@if (viewMode === 'card' && cardOpenMenuIndex !== null && moreMenuActions.length > 0) {\n <div class=\"osl-card-menu\"\n [style.top.px]=\"cardMenuPosition.top\"\n [style.left.px]=\"cardMenuPosition.left\"\n (click)=\"$event.stopPropagation()\">\n <div class=\"osl-card-menu-header\">Actions</div>\n @for (action of moreMenuActions; track $index) {\n @if (!action.hideIf || !action.hideIf(cardDatasource[cardOpenMenuIndex])) {\n <button class=\"osl-card-menu-item\"\n (click)=\"action.click(cardDatasource[cardOpenMenuIndex]); cardOpenMenuIndex = null\">\n <span class=\"osl-card-menu-dot\"></span>\n {{ action.labelIf ? action.labelIf(cardDatasource[cardOpenMenuIndex]) : action.label }}\n </button>\n }\n }\n </div>\n}\n", styles: [".osl-setup-header{display:flex;align-items:center;justify-content:space-between}.osl-setup-dialog-footer{display:flex;align-items:center;justify-content:flex-end;gap:10px;width:100%}.dialog-cancel-btn{display:flex;align-items:center;gap:6px;height:38px;font-size:13px;font-weight:500;border-color:var(--osl-border-color, #d1d5db);color:var(--osl-secondary, #6b7280);border-radius:var(--osl-border-radius, 4px);padding:0 16px;transition:all .2s ease}.dialog-cancel-btn mat-icon{font-size:17px;width:17px;height:17px}.dialog-cancel-btn:hover{border-color:#9ca3af;background:#f9fafb;color:#374151}.dialog-save-btn{display:flex;align-items:center;gap:6px;height:38px;font-size:13px;font-weight:500;background:linear-gradient(135deg,var(--osl-primary, #2563eb),#3b82f6);color:#fff;border-radius:var(--osl-border-radius, 4px);padding:0 18px;transition:all .2s ease;box-shadow:0 2px 8px #2563eb40}.dialog-save-btn mat-icon{font-size:17px;width:17px;height:17px}.dialog-save-btn:hover{background:linear-gradient(135deg,var(--osl-primary-hover, #1d4ed8),#2563eb);box-shadow:0 4px 14px #2563eb66;transform:translateY(-1px)}.dialog-save-btn:active{transform:translateY(0);box-shadow:0 2px 8px #2563eb40}.osl-view-toggle{display:flex;border:1.5px solid var(--osl-border-color, #e5e7eb);border-radius:8px;overflow:hidden;flex-shrink:0;background:#fff}.osl-view-toggle-btn{display:inline-flex;align-items:center;justify-content:center;width:34px;height:34px;border:none;background:transparent;cursor:pointer;color:#9ca3af;transition:background .15s,color .15s;padding:0}.osl-view-toggle-btn svg{width:16px;height:16px;transition:stroke .15s}.osl-view-toggle-btn:first-child{border-right:1.5px solid var(--osl-border-color, #e5e7eb)}.osl-view-toggle-btn--active{background:var(--osl-primary, #6366f1);color:#fff}.osl-view-toggle-btn--active svg{stroke:#fff}.osl-view-toggle-btn:not(.osl-view-toggle-btn--active):hover{background:#f3f4f6;color:#374151}.osl-card-container{overflow-y:auto;overflow-x:hidden;border-radius:12px;padding:4px 2px 16px;box-sizing:border-box}.osl-card-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:16px;padding:4px}@keyframes osl-card-enter{0%{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}.osl-card-grid .osl-card:nth-child(1){animation-delay:0s}.osl-card-grid .osl-card:nth-child(2){animation-delay:.04s}.osl-card-grid .osl-card:nth-child(3){animation-delay:.08s}.osl-card-grid .osl-card:nth-child(4){animation-delay:.12s}.osl-card-grid .osl-card:nth-child(5){animation-delay:.16s}.osl-card-grid .osl-card:nth-child(6){animation-delay:.2s}.osl-card-grid .osl-card:nth-child(7){animation-delay:.24s}.osl-card-grid .osl-card:nth-child(8){animation-delay:.28s}.osl-card-grid .osl-card:nth-child(9){animation-delay:.32s}.osl-card-grid .osl-card:nth-child(10){animation-delay:.36s}.osl-card-grid .osl-card:nth-child(11){animation-delay:.4s}.osl-card-grid .osl-card:nth-child(12){animation-delay:.44s}.osl-card{position:relative;background:#fff;border-radius:14px;border:1px solid #eaecf0;box-shadow:0 1px 4px #1018280f,0 1px 2px #1018280a;overflow:hidden;transition:transform .22s cubic-bezier(.34,1.56,.64,1),box-shadow .22s ease;animation:osl-card-enter .28s ease both}.osl-card:nth-child(6n+1){--card-accent: #6366f1;--card-accent-bg: #eef2ff}.osl-card:nth-child(6n+2){--card-accent: #0ea5e9;--card-accent-bg: #f0f9ff}.osl-card:nth-child(6n+3){--card-accent: #10b981;--card-accent-bg: #ecfdf5}.osl-card:nth-child(6n+4){--card-accent: #f59e0b;--card-accent-bg: #fffbeb}.osl-card:nth-child(6n+5){--card-accent: #f43f5e;--card-accent-bg: #fff1f2}.osl-card:nth-child(6n){--card-accent: #8b5cf6;--card-accent-bg: #faf5ff}.osl-card:before{content:\"\";position:absolute;top:0;left:0;bottom:0;width:4px;background:var(--card-accent, var(--osl-primary, #6366f1))}.osl-card:hover{transform:translateY(-4px);box-shadow:0 12px 32px #1018281a,0 4px 12px #1018280f;border-color:#d0d5dd}.osl-card--highlighted{outline:2px solid var(--osl-primary, #6366f1);outline-offset:2px;animation:osl-card-enter .28s ease both,osl-card-highlight-pulse 2.5s ease-out .3s both}.osl-card--selectable{cursor:pointer}.osl-card--skeleton{pointer-events:none;animation:osl-card-enter .28s ease both}.osl-card--skeleton:hover{transform:none;box-shadow:0 1px 4px #1018280f}@keyframes osl-card-highlight-pulse{0%{box-shadow:0 0 0 4px #6366f14d}to{box-shadow:none}}.osl-card-inner{padding:16px 16px 16px 20px;display:flex;flex-direction:column;height:100%}.osl-card-header{display:flex;align-items:center;gap:12px;margin-bottom:0}.osl-card-avatar{width:40px;height:40px;border-radius:10px;background:var(--card-accent, var(--osl-primary, #6366f1));color:#fff;font-size:16px;font-weight:700;display:flex;align-items:center;justify-content:center;flex-shrink:0;letter-spacing:0;box-shadow:0 4px 10px #00000026}.osl-card-avatar--skeleton{background:#e5e7eb;box-shadow:none;animation:osl-sk-pulse 1.4s ease-in-out infinite}.osl-card-title-wrap{flex:1;min-width:0}.osl-card-title{display:block;font-weight:700;font-size:14px;color:#101828;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;line-height:1.4}.osl-card-actions{display:flex;align-items:center;gap:6px;flex-shrink:0}.osl-card-action-btn{display:inline-flex;align-items:center;justify-content:center;width:30px;height:30px;border-radius:8px;border:1.5px solid #e4e7ec;background:#fff;cursor:pointer;transition:background .15s,border-color .15s,transform .15s,box-shadow .15s;padding:0}.osl-card-action-btn svg{width:14px;height:14px;transition:stroke .15s,fill .15s}.osl-card-action-btn--more svg{fill:#9ca3af;stroke:none}.osl-card-action-btn--more:hover{background:var(--card-accent-bg, #eef2ff);border-color:var(--card-accent, var(--osl-primary, #6366f1));box-shadow:0 2px 6px #00000014;transform:scale(1.05)}.osl-card-action-btn--more:hover svg{fill:var(--card-accent, var(--osl-primary, #6366f1))}.osl-card-action-btn--edit svg{stroke:#6b7280}.osl-card-action-btn--edit:hover{background:var(--card-accent-bg, #eef2ff);border-color:var(--card-accent, var(--osl-primary, #6366f1));box-shadow:0 2px 6px #00000014;transform:scale(1.05)}.osl-card-action-btn--edit:hover svg{stroke:var(--card-accent, var(--osl-primary, #6366f1))}.osl-card-action-btn--delete svg{stroke:#9ca3af}.osl-card-action-btn--delete:hover{background:#fff1f2;border-color:#f43f5e;box-shadow:0 2px 6px #f43f5e26;transform:scale(1.05)}.osl-card-action-btn--delete:hover svg{stroke:#f43f5e}.osl-card-divider{height:1px;background:#f2f4f7;margin:14px 0}.osl-card-body{display:flex;flex-direction:column;gap:10px;flex:1}.osl-card-field{display:grid;grid-template-columns:100px 1fr;gap:6px;align-items:baseline}.osl-card-label{font-size:10px;font-weight:600;color:#9ca3af;text-transform:uppercase;letter-spacing:.06em;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.osl-card-value{font-size:13px;font-weight:500;color:#374151;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}.osl-card-value.link{text-decoration:underline;color:#2563eb;cursor:pointer}.osl-card-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:320px;gap:12px;padding:40px 20px}.osl-card-empty-svg{width:180px;height:auto;opacity:.85}.osl-card-empty-title{font-size:16px;font-weight:600;color:#374151;margin:4px 0 0;text-align:center}.osl-card-empty-desc{font-size:13px;color:#9ca3af;margin:0;text-align:center}.osl-card-loader{display:flex;flex-direction:column;align-items:center;gap:10px;padding:28px 20px 16px}.osl-card-loader-track{width:160px;height:3px;background:#f3f4f6;border-radius:99px;overflow:hidden}.osl-card-loader-bar{height:100%;width:40%;background:linear-gradient(90deg,transparent,var(--osl-primary, #6366f1),transparent);border-radius:99px;animation:osl-loader-sweep 1.2s ease-in-out infinite}@keyframes osl-loader-sweep{0%{transform:translate(-200%)}to{transform:translate(500%)}}.osl-card-loader-text{font-size:12px;color:#9ca3af;font-weight:500}.osl-card-all-loaded{display:flex;align-items:center;gap:12px;padding:20px 16px 8px;justify-content:center}.osl-card-all-loaded-line{flex:1;max-width:80px;height:1px;background:#e5e7eb}.osl-card-all-loaded-text{font-size:11px;color:#d1d5db;font-weight:600;text-transform:uppercase;letter-spacing:.06em;white-space:nowrap}@keyframes osl-sk-pulse{0%,to{opacity:1}50%{opacity:.4}}.osl-sk-line{height:12px;background:#e9eaec;border-radius:6px;animation:osl-sk-pulse 1.4s ease-in-out infinite;width:80%}.osl-sk-line--title{height:14px;width:65%}.osl-sk-line--label{width:40%;height:10px}.osl-sk-line--short{width:50%}.osl-card-menu{position:fixed;z-index:9999;min-width:196px;background:#fff;border:1px solid #eaecf0;border-radius:12px;box-shadow:0 12px 32px #10182824,0 4px 12px #1018280f;overflow:hidden;animation:osl-menu-pop .16s cubic-bezier(.16,1,.3,1)}@keyframes osl-menu-pop{0%{opacity:0;transform:translateY(-8px) scale(.96)}to{opacity:1;transform:translateY(0) scale(1)}}.osl-card-menu-header{padding:10px 14px 7px;font-size:10px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:#9ca3af;border-bottom:1px solid #f2f4f7;-webkit-user-select:none;user-select:none}.osl-card-menu-item{display:flex;align-items:center;gap:10px;width:100%;padding:10px 14px;background:transparent;border:none;border-left:3px solid transparent;border-bottom:1px solid #f9fafb;text-align:left;font-size:13px;font-weight:500;font-family:inherit;color:#374151;cursor:pointer;white-space:nowrap;transition:background .12s,color .12s,border-left-color .12s}.osl-card-menu-item:last-child{border-bottom:none}.osl-card-menu-item:hover{background:var(--card-accent-bg, #f5f3ff);color:var(--card-accent, var(--osl-primary, #6366f1));border-left-color:var(--card-accent, var(--osl-primary, #6366f1))}.osl-card-menu-item:hover .osl-card-menu-dot{background:var(--card-accent, var(--osl-primary, #6366f1));box-shadow:0 0 0 3px #6366f129}.osl-card-menu-dot{flex-shrink:0;width:6px;height:6px;border-radius:50%;background:#d1d5db;transition:background .12s,box-shadow .12s}\n"], dependencies: [{ kind: "directive", type: i1$2.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: DynamicForm, selector: "osl-dynamic-form", inputs: ["elements", "model", "skeletonLoading", "skeletonTheme"], outputs: ["modelChange"] }, { kind: "component", type: OslButton, selector: "osl-button", inputs: ["label", "icon", "variant", "size", "disabled", "loading", "type", "fullWidth"], outputs: ["clickEv"] }, { kind: "component", type: OslSearchbar, selector: "osl-searchbar", inputs: ["label"], outputs: ["onSearch"] }, { kind: "component", type: OslGrid, selector: "osl-grid", inputs: ["columns", "datasource", "isPaginated", "pageSize", "autoMode", "totalRecords", "tableHeight", "loading", "isSelectable", "moreMenuActions", "canEdit", "canDelete", "highlightedRow", "primaryKey"], outputs: ["datasourceChange", "pageChange", "pageSizeChange", "sortChange", "editClick", "deleteClick", "onRowClick"] }, { kind: "pipe", type: i1$2.DatePipe, name: "date" }] });
|
|
3959
|
+
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 }], 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 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 (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 [oslTooltip]=\"cardTitleColumn ? getCellValue(row, cardTitleColumn) : ''\"\n oslTooltipPosition=\"bottom\">\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 [oslTooltip]=\"getCellValue(row, col)\" [oslTooltipPosition]=\"'bottom'\">\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" }] });
|
|
3851
3960
|
}
|
|
3852
3961
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: OslSetup, decorators: [{
|
|
3853
3962
|
type: Component,
|
|
3854
|
-
args: [{ selector: 'osl-setup', standalone: false, template: "<div class=\"p-2\">\n\n <!-- \u2500\u2500 Header \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div class=\"osl-setup-header\">\n <h5 class=\"mb-0\">{{ title }}</h5>\n <div class=\"d-flex align-items-center gap-2\">\n <osl-searchbar #searchbar class=\"mx-2\" (onSearch)=\"onSearchSetup($event)\"></osl-searchbar>\n\n <!-- View Toggle -->\n @if(!isLister){\n <div class=\"osl-view-toggle\">\n <button class=\"osl-view-toggle-btn\" [class.osl-view-toggle-btn--active]=\"viewMode === 'table'\"\n (click)=\"toggleView('table')\" title=\"Table view\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"2\"/>\n <line x1=\"3\" y1=\"9\" x2=\"21\" y2=\"9\"/>\n <line x1=\"3\" y1=\"15\" x2=\"21\" y2=\"15\"/>\n <line x1=\"9\" y1=\"9\" x2=\"9\" y2=\"21\"/>\n </svg>\n </button>\n <button class=\"osl-view-toggle-btn\" [class.osl-view-toggle-btn--active]=\"viewMode === 'card'\"\n (click)=\"toggleView('card')\" title=\"Card view\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <rect x=\"3\" y=\"3\" width=\"7\" height=\"7\" rx=\"1.5\"/>\n <rect x=\"14\" y=\"3\" width=\"7\" height=\"7\" rx=\"1.5\"/>\n <rect x=\"3\" y=\"14\" width=\"7\" height=\"7\" rx=\"1.5\"/>\n <rect x=\"14\" y=\"14\" width=\"7\" height=\"7\" rx=\"1.5\"/>\n </svg>\n </button>\n </div>\n }\n\n @if (!isLister && canAdd) {\n <osl-button variant=\"secondary\" size=\"sm\" [label]=\"'Add ' + title\" (clickEv)=\"openAddDialog()\"></osl-button>\n }\n </div>\n </div>\n\n <!-- \u2500\u2500 Table View \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (viewMode === 'table') {\n <div class=\"osl-setup-body my-2\">\n <osl-grid\n #gridRef\n [columns]=\"columnsWithActions\"\n [(datasource)]=\"datasource\"\n [isPaginated]=\"isPaginated\"\n [pageSize]=\"pageSize\"\n [autoMode]=\"autoMode\"\n [tableHeight]=\"tableHeight\"\n [totalRecords]=\"totalRecords\"\n [loading]=\"loading\"\n [moreMenuActions]=\"moreMenuActions\"\n [canEdit]=\"canEdit\"\n [canDelete]=\"canDelete\"\n [highlightedRow]=\"restoredRow\"\n [primaryKey]=\"primaryKey\"\n (editClick)=\"openEditDialog($event)\"\n (deleteClick)=\"onDeleteClick($event)\"\n (pageChange)=\"onPageChange(pageChange, $event)\"\n (pageSizeChange)=\"onPageChange(pageSizeChange, $event)\"\n (sortChange)=\"sortChange.emit($event)\"\n [isSelectable]=\"isLister\"\n (onRowClick)=\"onRowClick.emit($event)\"\n />\n </div>\n }\n\n <!-- \u2500\u2500 Card View \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (viewMode === 'card') {\n <div #cardContainerRef class=\"osl-card-container my-2\" [style.height]=\"tableHeight\" (scroll)=\"onCardScroll($event)\">\n\n <!-- Skeleton cards on initial load -->\n @if (loading && cardDatasource.length === 0) {\n <div class=\"osl-card-grid\">\n @for (sk of skeletonCardRows; track $index) {\n <div class=\"osl-card osl-card--skeleton\">\n <div class=\"osl-card-inner\">\n <div class=\"osl-card-header\">\n <div class=\"osl-card-avatar osl-card-avatar--skeleton\"></div>\n <div style=\"flex:1\">\n <div class=\"osl-sk-line osl-sk-line--title\"></div>\n </div>\n </div>\n <div class=\"osl-card-divider\"></div>\n <div class=\"osl-card-body\">\n <div class=\"osl-card-field\"><div class=\"osl-sk-line osl-sk-line--label\"></div><div class=\"osl-sk-line\"></div></div>\n <div class=\"osl-card-field\"><div class=\"osl-sk-line osl-sk-line--label\"></div><div class=\"osl-sk-line osl-sk-line--short\"></div></div>\n <div class=\"osl-card-field\"><div class=\"osl-sk-line osl-sk-line--label\"></div><div class=\"osl-sk-line\"></div></div>\n </div>\n </div>\n </div>\n }\n </div>\n }\n\n <!-- Empty state -->\n @else if (cardDatasource.length === 0 && !loading) {\n <div class=\"osl-card-empty\">\n <svg class=\"osl-card-empty-svg\" viewBox=\"0 0 180 160\" fill=\"none\">\n <ellipse cx=\"90\" cy=\"140\" rx=\"60\" ry=\"8\" fill=\"#f3f4f6\"/>\n <rect x=\"30\" y=\"30\" width=\"120\" height=\"96\" rx=\"10\" fill=\"#f9fafb\" stroke=\"#e5e7eb\" stroke-width=\"2\"/>\n <rect x=\"44\" y=\"48\" width=\"92\" height=\"10\" rx=\"5\" fill=\"#e5e7eb\"/>\n <rect x=\"44\" y=\"66\" width=\"72\" height=\"8\" rx=\"4\" fill=\"#f3f4f6\"/>\n <rect x=\"44\" y=\"82\" width=\"84\" height=\"8\" rx=\"4\" fill=\"#f3f4f6\"/>\n <rect x=\"44\" y=\"98\" width=\"56\" height=\"8\" rx=\"4\" fill=\"#f3f4f6\"/>\n <circle cx=\"140\" cy=\"108\" r=\"26\" fill=\"#fff\" stroke=\"#e5e7eb\" stroke-width=\"2\"/>\n <line x1=\"133\" y1=\"101\" x2=\"147\" y2=\"115\" stroke=\"#d1d5db\" stroke-width=\"2.5\" stroke-linecap=\"round\"/>\n <line x1=\"147\" y1=\"101\" x2=\"133\" y2=\"115\" stroke=\"#d1d5db\" stroke-width=\"2.5\" stroke-linecap=\"round\"/>\n </svg>\n <p class=\"osl-card-empty-title\">No records found</p>\n <p class=\"osl-card-empty-desc\">Try adjusting your search or filter criteria</p>\n </div>\n }\n\n <!-- Card grid -->\n @else {\n <div class=\"osl-card-grid\">\n\n @if (cardTemplate) {\n @for (row of cardDatasource; track row[primaryKey]; let i = $index) {\n <ng-container *ngTemplateOutlet=\"cardTemplate; context: { $implicit: row, index: i }\"></ng-container>\n }\n } @else {\n @for (row of cardDatasource; track row[primaryKey]; let i = $index) {\n <div class=\"osl-card\"\n [class.osl-card--highlighted]=\"isHighlightedCard(row)\"\n [class.osl-card--selectable]=\"isLister\"\n (click)=\"isLister ? onRowClick.emit(row) : null\">\n\n <div class=\"osl-card-inner\">\n\n <!-- Card header -->\n <div class=\"osl-card-header\">\n <!-- Avatar circle -->\n @if (cardTitleColumn) {\n <div class=\"osl-card-avatar\">{{ getCardInitial(row) }}</div>\n }\n\n <!-- Title -->\n <div class=\"osl-card-title-wrap\">\n <span class=\"osl-card-title\">\n @if (cardTitleColumn) {\n @switch (cardTitleColumn.displayType) {\n @case ('date') { {{ row[cardTitleColumn.key] | date }} }\n @case ('datetime') { {{ row[cardTitleColumn.key] | date:'medium' }} }\n @case ('time') { {{ row[cardTitleColumn.key] | date:'shortTime' }} }\n @case ('customDateFormat') { {{ row[cardTitleColumn.key] | date:cardTitleColumn.customDateFormat }} }\n @default { {{ getCellValue(row, cardTitleColumn) }} }\n }\n }\n </span>\n </div>\n\n <!-- Action buttons -->\n @if (!isLister && ((hasForm && (canEdit || canDelete)) || (moreMenuActions.length > 0 && hasVisibleActions(row)))) {\n <div class=\"osl-card-actions\">\n\n <!-- More-actions trigger (menu rendered at root to avoid transform containment) -->\n @if (moreMenuActions.length > 0 && hasVisibleActions(row)) {\n <button class=\"osl-card-action-btn osl-card-action-btn--more\"\n (click)=\"toggleCardMenu(i, $event)\" title=\"More actions\">\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <circle cx=\"12\" cy=\"5\" r=\"2.2\"/>\n <circle cx=\"12\" cy=\"12\" r=\"2.2\"/>\n <circle cx=\"12\" cy=\"19\" r=\"2.2\"/>\n </svg>\n </button>\n }\n\n <!-- Edit -->\n @if (hasForm && canEdit) {\n <button class=\"osl-card-action-btn osl-card-action-btn--edit\"\n (click)=\"$event.stopPropagation(); openEditDialog(row)\" title=\"Edit\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7\"/>\n <path d=\"M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z\"/>\n </svg>\n </button>\n }\n\n <!-- Delete -->\n @if (hasForm && canDelete) {\n <button class=\"osl-card-action-btn osl-card-action-btn--delete\"\n (click)=\"$event.stopPropagation(); onDeleteClick(row)\" title=\"Delete\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polyline points=\"3 6 5 6 21 6\"/>\n <path d=\"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2\"/>\n </svg>\n </button>\n }\n </div>\n }\n </div>\n\n <!-- Divider -->\n <div class=\"osl-card-divider\"></div>\n\n <!-- Card body: remaining fields -->\n <div class=\"osl-card-body\">\n @for (col of cardBodyColumns; track col.key) {\n <div class=\"osl-card-field\">\n <span class=\"osl-card-label\">{{ col.label }}</span>\n <span class=\"osl-card-value\" [class.link]=\"col.displayType === 'link'\" (click)=\"col.click ? col.click(row, col) : null\">\n @switch (col.displayType) {\n @case ('date') { {{ row[col.key] | date }} }\n @case ('datetime') { {{ row[col.key] | date:'medium' }} }\n @case ('time') { {{ row[col.key] | date:'shortTime' }} }\n @case ('customDateFormat') { {{ row[col.key] | date:col.customDateFormat }} }\n\n @default { {{ getCellValue(row, col) }} }\n }\n </span>\n </div>\n }\n </div>\n\n </div>\n </div>\n }\n }\n </div>\n\n <!-- Infinite scroll bottom loader -->\n @if (loading) {\n <div class=\"osl-card-loader\">\n <div class=\"osl-card-loader-track\">\n <div class=\"osl-card-loader-bar\"></div>\n </div>\n <span class=\"osl-card-loader-text\">Loading more\u2026</span>\n </div>\n }\n\n <!-- All loaded footer -->\n @if (allCardsLoaded && !loading && cardDatasource.length > 0) {\n <div class=\"osl-card-all-loaded\">\n <span class=\"osl-card-all-loaded-line\"></span>\n <span class=\"osl-card-all-loaded-text\">All {{ totalRecords || cardDatasource.length }} records loaded</span>\n <span class=\"osl-card-all-loaded-line\"></span>\n </div>\n }\n }\n </div>\n }\n\n</div>\n\n<!-- Dialog: Form Body -->\n<ng-template #formBodyTpl>\n <osl-dynamic-form\n [skeletonLoading]=\"formLoading\"\n [elements]=\"formElements\"\n [(model)]=\"dialogModel\"\n ></osl-dynamic-form>\n</ng-template>\n\n<!-- Dialog: Form Footer -->\n<ng-template #formFooterTpl>\n <div class=\"osl-setup-dialog-footer\">\n <osl-button [loading]=\"saveLoading\" variant=\"secondary\" label=\"Save\" (click)=\"saveDialog()\"></osl-button>\n </div>\n</ng-template>\n\n<ng-template #customFooterWrapperTpl let-data>\n <ng-container *ngTemplateOutlet=\"customFormFooter!; context: { $implicit: { dialogModel: dialogModel, dialogMode: dialogMode, dialogRef: data.dialogRef } }\"></ng-container>\n</ng-template>\n\n<!-- Floating card more-actions menu \u2014 rendered outside card DOM to avoid transform containment -->\n@if (viewMode === 'card' && cardOpenMenuIndex !== null && moreMenuActions.length > 0) {\n <div class=\"osl-card-menu\"\n [style.top.px]=\"cardMenuPosition.top\"\n [style.left.px]=\"cardMenuPosition.left\"\n (click)=\"$event.stopPropagation()\">\n <div class=\"osl-card-menu-header\">Actions</div>\n @for (action of moreMenuActions; track $index) {\n @if (!action.hideIf || !action.hideIf(cardDatasource[cardOpenMenuIndex])) {\n <button class=\"osl-card-menu-item\"\n (click)=\"action.click(cardDatasource[cardOpenMenuIndex]); cardOpenMenuIndex = null\">\n <span class=\"osl-card-menu-dot\"></span>\n {{ action.labelIf ? action.labelIf(cardDatasource[cardOpenMenuIndex]) : action.label }}\n </button>\n }\n }\n </div>\n}\n", styles: [".osl-setup-header{display:flex;align-items:center;justify-content:space-between}.osl-setup-dialog-footer{display:flex;align-items:center;justify-content:flex-end;gap:10px;width:100%}.dialog-cancel-btn{display:flex;align-items:center;gap:6px;height:38px;font-size:13px;font-weight:500;border-color:var(--osl-border-color, #d1d5db);color:var(--osl-secondary, #6b7280);border-radius:var(--osl-border-radius, 4px);padding:0 16px;transition:all .2s ease}.dialog-cancel-btn mat-icon{font-size:17px;width:17px;height:17px}.dialog-cancel-btn:hover{border-color:#9ca3af;background:#f9fafb;color:#374151}.dialog-save-btn{display:flex;align-items:center;gap:6px;height:38px;font-size:13px;font-weight:500;background:linear-gradient(135deg,var(--osl-primary, #2563eb),#3b82f6);color:#fff;border-radius:var(--osl-border-radius, 4px);padding:0 18px;transition:all .2s ease;box-shadow:0 2px 8px #2563eb40}.dialog-save-btn mat-icon{font-size:17px;width:17px;height:17px}.dialog-save-btn:hover{background:linear-gradient(135deg,var(--osl-primary-hover, #1d4ed8),#2563eb);box-shadow:0 4px 14px #2563eb66;transform:translateY(-1px)}.dialog-save-btn:active{transform:translateY(0);box-shadow:0 2px 8px #2563eb40}.osl-view-toggle{display:flex;border:1.5px solid var(--osl-border-color, #e5e7eb);border-radius:8px;overflow:hidden;flex-shrink:0;background:#fff}.osl-view-toggle-btn{display:inline-flex;align-items:center;justify-content:center;width:34px;height:34px;border:none;background:transparent;cursor:pointer;color:#9ca3af;transition:background .15s,color .15s;padding:0}.osl-view-toggle-btn svg{width:16px;height:16px;transition:stroke .15s}.osl-view-toggle-btn:first-child{border-right:1.5px solid var(--osl-border-color, #e5e7eb)}.osl-view-toggle-btn--active{background:var(--osl-primary, #6366f1);color:#fff}.osl-view-toggle-btn--active svg{stroke:#fff}.osl-view-toggle-btn:not(.osl-view-toggle-btn--active):hover{background:#f3f4f6;color:#374151}.osl-card-container{overflow-y:auto;overflow-x:hidden;border-radius:12px;padding:4px 2px 16px;box-sizing:border-box}.osl-card-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:16px;padding:4px}@keyframes osl-card-enter{0%{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}.osl-card-grid .osl-card:nth-child(1){animation-delay:0s}.osl-card-grid .osl-card:nth-child(2){animation-delay:.04s}.osl-card-grid .osl-card:nth-child(3){animation-delay:.08s}.osl-card-grid .osl-card:nth-child(4){animation-delay:.12s}.osl-card-grid .osl-card:nth-child(5){animation-delay:.16s}.osl-card-grid .osl-card:nth-child(6){animation-delay:.2s}.osl-card-grid .osl-card:nth-child(7){animation-delay:.24s}.osl-card-grid .osl-card:nth-child(8){animation-delay:.28s}.osl-card-grid .osl-card:nth-child(9){animation-delay:.32s}.osl-card-grid .osl-card:nth-child(10){animation-delay:.36s}.osl-card-grid .osl-card:nth-child(11){animation-delay:.4s}.osl-card-grid .osl-card:nth-child(12){animation-delay:.44s}.osl-card{position:relative;background:#fff;border-radius:14px;border:1px solid #eaecf0;box-shadow:0 1px 4px #1018280f,0 1px 2px #1018280a;overflow:hidden;transition:transform .22s cubic-bezier(.34,1.56,.64,1),box-shadow .22s ease;animation:osl-card-enter .28s ease both}.osl-card:nth-child(6n+1){--card-accent: #6366f1;--card-accent-bg: #eef2ff}.osl-card:nth-child(6n+2){--card-accent: #0ea5e9;--card-accent-bg: #f0f9ff}.osl-card:nth-child(6n+3){--card-accent: #10b981;--card-accent-bg: #ecfdf5}.osl-card:nth-child(6n+4){--card-accent: #f59e0b;--card-accent-bg: #fffbeb}.osl-card:nth-child(6n+5){--card-accent: #f43f5e;--card-accent-bg: #fff1f2}.osl-card:nth-child(6n){--card-accent: #8b5cf6;--card-accent-bg: #faf5ff}.osl-card:before{content:\"\";position:absolute;top:0;left:0;bottom:0;width:4px;background:var(--card-accent, var(--osl-primary, #6366f1))}.osl-card:hover{transform:translateY(-4px);box-shadow:0 12px 32px #1018281a,0 4px 12px #1018280f;border-color:#d0d5dd}.osl-card--highlighted{outline:2px solid var(--osl-primary, #6366f1);outline-offset:2px;animation:osl-card-enter .28s ease both,osl-card-highlight-pulse 2.5s ease-out .3s both}.osl-card--selectable{cursor:pointer}.osl-card--skeleton{pointer-events:none;animation:osl-card-enter .28s ease both}.osl-card--skeleton:hover{transform:none;box-shadow:0 1px 4px #1018280f}@keyframes osl-card-highlight-pulse{0%{box-shadow:0 0 0 4px #6366f14d}to{box-shadow:none}}.osl-card-inner{padding:16px 16px 16px 20px;display:flex;flex-direction:column;height:100%}.osl-card-header{display:flex;align-items:center;gap:12px;margin-bottom:0}.osl-card-avatar{width:40px;height:40px;border-radius:10px;background:var(--card-accent, var(--osl-primary, #6366f1));color:#fff;font-size:16px;font-weight:700;display:flex;align-items:center;justify-content:center;flex-shrink:0;letter-spacing:0;box-shadow:0 4px 10px #00000026}.osl-card-avatar--skeleton{background:#e5e7eb;box-shadow:none;animation:osl-sk-pulse 1.4s ease-in-out infinite}.osl-card-title-wrap{flex:1;min-width:0}.osl-card-title{display:block;font-weight:700;font-size:14px;color:#101828;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;line-height:1.4}.osl-card-actions{display:flex;align-items:center;gap:6px;flex-shrink:0}.osl-card-action-btn{display:inline-flex;align-items:center;justify-content:center;width:30px;height:30px;border-radius:8px;border:1.5px solid #e4e7ec;background:#fff;cursor:pointer;transition:background .15s,border-color .15s,transform .15s,box-shadow .15s;padding:0}.osl-card-action-btn svg{width:14px;height:14px;transition:stroke .15s,fill .15s}.osl-card-action-btn--more svg{fill:#9ca3af;stroke:none}.osl-card-action-btn--more:hover{background:var(--card-accent-bg, #eef2ff);border-color:var(--card-accent, var(--osl-primary, #6366f1));box-shadow:0 2px 6px #00000014;transform:scale(1.05)}.osl-card-action-btn--more:hover svg{fill:var(--card-accent, var(--osl-primary, #6366f1))}.osl-card-action-btn--edit svg{stroke:#6b7280}.osl-card-action-btn--edit:hover{background:var(--card-accent-bg, #eef2ff);border-color:var(--card-accent, var(--osl-primary, #6366f1));box-shadow:0 2px 6px #00000014;transform:scale(1.05)}.osl-card-action-btn--edit:hover svg{stroke:var(--card-accent, var(--osl-primary, #6366f1))}.osl-card-action-btn--delete svg{stroke:#9ca3af}.osl-card-action-btn--delete:hover{background:#fff1f2;border-color:#f43f5e;box-shadow:0 2px 6px #f43f5e26;transform:scale(1.05)}.osl-card-action-btn--delete:hover svg{stroke:#f43f5e}.osl-card-divider{height:1px;background:#f2f4f7;margin:14px 0}.osl-card-body{display:flex;flex-direction:column;gap:10px;flex:1}.osl-card-field{display:grid;grid-template-columns:100px 1fr;gap:6px;align-items:baseline}.osl-card-label{font-size:10px;font-weight:600;color:#9ca3af;text-transform:uppercase;letter-spacing:.06em;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.osl-card-value{font-size:13px;font-weight:500;color:#374151;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}.osl-card-value.link{text-decoration:underline;color:#2563eb;cursor:pointer}.osl-card-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:320px;gap:12px;padding:40px 20px}.osl-card-empty-svg{width:180px;height:auto;opacity:.85}.osl-card-empty-title{font-size:16px;font-weight:600;color:#374151;margin:4px 0 0;text-align:center}.osl-card-empty-desc{font-size:13px;color:#9ca3af;margin:0;text-align:center}.osl-card-loader{display:flex;flex-direction:column;align-items:center;gap:10px;padding:28px 20px 16px}.osl-card-loader-track{width:160px;height:3px;background:#f3f4f6;border-radius:99px;overflow:hidden}.osl-card-loader-bar{height:100%;width:40%;background:linear-gradient(90deg,transparent,var(--osl-primary, #6366f1),transparent);border-radius:99px;animation:osl-loader-sweep 1.2s ease-in-out infinite}@keyframes osl-loader-sweep{0%{transform:translate(-200%)}to{transform:translate(500%)}}.osl-card-loader-text{font-size:12px;color:#9ca3af;font-weight:500}.osl-card-all-loaded{display:flex;align-items:center;gap:12px;padding:20px 16px 8px;justify-content:center}.osl-card-all-loaded-line{flex:1;max-width:80px;height:1px;background:#e5e7eb}.osl-card-all-loaded-text{font-size:11px;color:#d1d5db;font-weight:600;text-transform:uppercase;letter-spacing:.06em;white-space:nowrap}@keyframes osl-sk-pulse{0%,to{opacity:1}50%{opacity:.4}}.osl-sk-line{height:12px;background:#e9eaec;border-radius:6px;animation:osl-sk-pulse 1.4s ease-in-out infinite;width:80%}.osl-sk-line--title{height:14px;width:65%}.osl-sk-line--label{width:40%;height:10px}.osl-sk-line--short{width:50%}.osl-card-menu{position:fixed;z-index:9999;min-width:196px;background:#fff;border:1px solid #eaecf0;border-radius:12px;box-shadow:0 12px 32px #10182824,0 4px 12px #1018280f;overflow:hidden;animation:osl-menu-pop .16s cubic-bezier(.16,1,.3,1)}@keyframes osl-menu-pop{0%{opacity:0;transform:translateY(-8px) scale(.96)}to{opacity:1;transform:translateY(0) scale(1)}}.osl-card-menu-header{padding:10px 14px 7px;font-size:10px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:#9ca3af;border-bottom:1px solid #f2f4f7;-webkit-user-select:none;user-select:none}.osl-card-menu-item{display:flex;align-items:center;gap:10px;width:100%;padding:10px 14px;background:transparent;border:none;border-left:3px solid transparent;border-bottom:1px solid #f9fafb;text-align:left;font-size:13px;font-weight:500;font-family:inherit;color:#374151;cursor:pointer;white-space:nowrap;transition:background .12s,color .12s,border-left-color .12s}.osl-card-menu-item:last-child{border-bottom:none}.osl-card-menu-item:hover{background:var(--card-accent-bg, #f5f3ff);color:var(--card-accent, var(--osl-primary, #6366f1));border-left-color:var(--card-accent, var(--osl-primary, #6366f1))}.osl-card-menu-item:hover .osl-card-menu-dot{background:var(--card-accent, var(--osl-primary, #6366f1));box-shadow:0 0 0 3px #6366f129}.osl-card-menu-dot{flex-shrink:0;width:6px;height:6px;border-radius:50%;background:#d1d5db;transition:background .12s,box-shadow .12s}\n"] }]
|
|
3963
|
+
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 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 (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 [oslTooltip]=\"cardTitleColumn ? getCellValue(row, cardTitleColumn) : ''\"\n oslTooltipPosition=\"bottom\">\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 [oslTooltip]=\"getCellValue(row, col)\" [oslTooltipPosition]=\"'bottom'\">\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"] }]
|
|
3855
3964
|
}], propDecorators: { formBodyTpl: [{
|
|
3856
3965
|
type: ViewChild,
|
|
3857
3966
|
args: ['formBodyTpl']
|
|
@@ -3867,9 +3976,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
3867
3976
|
}], gridRef: [{
|
|
3868
3977
|
type: ViewChild,
|
|
3869
3978
|
args: ['gridRef']
|
|
3870
|
-
}], cardContainerRef: [{
|
|
3871
|
-
type: ViewChild,
|
|
3872
|
-
args: ['cardContainerRef']
|
|
3873
3979
|
}], title: [{
|
|
3874
3980
|
type: Input,
|
|
3875
3981
|
args: ['title']
|
|
@@ -3948,6 +4054,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
3948
4054
|
}], cardTemplate: [{
|
|
3949
4055
|
type: Input,
|
|
3950
4056
|
args: ['cardTemplate']
|
|
4057
|
+
}], cardCol: [{
|
|
4058
|
+
type: Input,
|
|
4059
|
+
args: ['cardCol']
|
|
3951
4060
|
}], onSearch: [{
|
|
3952
4061
|
type: Output
|
|
3953
4062
|
}], onAdd: [{
|
|
@@ -4200,7 +4309,7 @@ class OslAutocompleteLister {
|
|
|
4200
4309
|
this.dialogRef.close(event);
|
|
4201
4310
|
}
|
|
4202
4311
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: OslAutocompleteLister, deps: [{ token: i1.MatDialogRef }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
|
|
4203
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.9", type: OslAutocompleteLister, isStandalone: false, selector: "osl-autocomplete-lister", inputs: { data: "data" }, viewQueries: [{ propertyName: "setup", first: true, predicate: ["setup"], descendants: true }], ngImport: i0, template: "<div class=\"p-4\">\r\n <osl-setup #setup [loading]=\"loader\" [autoMode]=\"false\" [title]=\"autocompleteData?.title\" (sortChange)=\"onSortChange($event)\" [totalRecords]=\"recordCount\" (pageSizeChange)=\"onPageChange($event)\" (pageChange)=\"onPageChange($event)\" (onRowClick)=\"onRowClick($event)\" [columns]=\"column\" [datasource]=\"datasource\" [isLister]=\"true\"></osl-setup>\r\n \r\n</div>", styles: [""], dependencies: [{ kind: "component", type: OslSetup, selector: "osl-setup", inputs: ["title", "columns", "datasource", "isPaginated", "pageSize", "autoMode", "tableHeight", "totalRecords", "loading", "dialogWidth", "formElements", "beforeDisplay", "onAddEditFn", "isLister", "canAdd", "canEdit", "canDelete", "moreMenuActions", "customFormFooter", "customHeaderTemp", "partialCustomHeaderTemp", "stateKey", "primaryKey", "onSave", "cardPageSize", "cardTemplate"], outputs: ["onSearch", "onAdd", "onEdit", "onDelete", "pageChange", "pageSizeChange", "sortChange", "onRowClick", "onStateRestored"] }] });
|
|
4312
|
+
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"] }] });
|
|
4204
4313
|
}
|
|
4205
4314
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: OslAutocompleteLister, decorators: [{
|
|
4206
4315
|
type: Component,
|
|
@@ -4226,201 +4335,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
4226
4335
|
}]
|
|
4227
4336
|
}] });
|
|
4228
4337
|
|
|
4229
|
-
const TOOLTIP_CSS = `
|
|
4230
|
-
.osl-tooltip {
|
|
4231
|
-
position: fixed;
|
|
4232
|
-
top: -9999px;
|
|
4233
|
-
left: -9999px;
|
|
4234
|
-
z-index: 10000;
|
|
4235
|
-
background: #111827;
|
|
4236
|
-
color: #ffffff;
|
|
4237
|
-
font-size: 12px;
|
|
4238
|
-
line-height: 1.55;
|
|
4239
|
-
font-family: inherit;
|
|
4240
|
-
padding: 6px 12px;
|
|
4241
|
-
border-radius: 8px;
|
|
4242
|
-
word-break: break-word;
|
|
4243
|
-
white-space: pre-wrap;
|
|
4244
|
-
box-shadow: 0 8px 24px rgba(0,0,0,0.22), 0 2px 6px rgba(0,0,0,0.14);
|
|
4245
|
-
pointer-events: none;
|
|
4246
|
-
}
|
|
4247
|
-
.osl-tooltip::after {
|
|
4248
|
-
content: '';
|
|
4249
|
-
position: absolute;
|
|
4250
|
-
border: 5px solid transparent;
|
|
4251
|
-
}
|
|
4252
|
-
.osl-tooltip--top::after {
|
|
4253
|
-
top: 100%;
|
|
4254
|
-
left: 50%;
|
|
4255
|
-
transform: translateX(-50%);
|
|
4256
|
-
border-top-color: #111827;
|
|
4257
|
-
}
|
|
4258
|
-
.osl-tooltip--bottom::after {
|
|
4259
|
-
bottom: 100%;
|
|
4260
|
-
left: 50%;
|
|
4261
|
-
transform: translateX(-50%);
|
|
4262
|
-
border-bottom-color: #111827;
|
|
4263
|
-
}
|
|
4264
|
-
.osl-tooltip--left::after {
|
|
4265
|
-
top: 50%;
|
|
4266
|
-
left: 100%;
|
|
4267
|
-
transform: translateY(-50%);
|
|
4268
|
-
border-left-color: #111827;
|
|
4269
|
-
}
|
|
4270
|
-
.osl-tooltip--right::after {
|
|
4271
|
-
top: 50%;
|
|
4272
|
-
right: 100%;
|
|
4273
|
-
transform: translateY(-50%);
|
|
4274
|
-
border-right-color: #111827;
|
|
4275
|
-
}
|
|
4276
|
-
|
|
4277
|
-
/* Default: position-aware slide animations */
|
|
4278
|
-
.osl-tooltip--top { animation: _osl_tip_up 0.18s cubic-bezier(0.16,1,0.3,1) forwards; }
|
|
4279
|
-
.osl-tooltip--bottom { animation: _osl_tip_down 0.18s cubic-bezier(0.16,1,0.3,1) forwards; }
|
|
4280
|
-
.osl-tooltip--left { animation: _osl_tip_left 0.18s cubic-bezier(0.16,1,0.3,1) forwards; }
|
|
4281
|
-
.osl-tooltip--right { animation: _osl_tip_right 0.18s cubic-bezier(0.16,1,0.3,1) forwards; }
|
|
4282
|
-
|
|
4283
|
-
/* Animation override variants */
|
|
4284
|
-
.osl-tooltip--anim-scale { animation: _osl_tip_scale 0.20s cubic-bezier(0.34,1.56,0.64,1) forwards !important; }
|
|
4285
|
-
.osl-tooltip--anim-bounce { animation: _osl_tip_bounce 0.40s cubic-bezier(0.34,1.56,0.64,1) forwards !important; }
|
|
4286
|
-
.osl-tooltip--anim-fade { animation: _osl_tip_fade 0.22s ease-out forwards !important; }
|
|
4287
|
-
|
|
4288
|
-
@keyframes _osl_tip_up { from { opacity:0; transform:translateY(8px); } to { opacity:1; transform:translateY(0); } }
|
|
4289
|
-
@keyframes _osl_tip_down { from { opacity:0; transform:translateY(-8px); } to { opacity:1; transform:translateY(0); } }
|
|
4290
|
-
@keyframes _osl_tip_left { from { opacity:0; transform:translateX(8px); } to { opacity:1; transform:translateX(0); } }
|
|
4291
|
-
@keyframes _osl_tip_right { from { opacity:0; transform:translateX(-8px); } to { opacity:1; transform:translateX(0); } }
|
|
4292
|
-
@keyframes _osl_tip_scale {
|
|
4293
|
-
from { opacity:0; transform:scale(0.72); }
|
|
4294
|
-
to { opacity:1; transform:scale(1); }
|
|
4295
|
-
}
|
|
4296
|
-
@keyframes _osl_tip_bounce {
|
|
4297
|
-
0% { opacity:0; transform:scale(0.55); }
|
|
4298
|
-
55% { opacity:1; transform:scale(1.12); }
|
|
4299
|
-
75% { transform:scale(0.95); }
|
|
4300
|
-
90% { transform:scale(1.03); }
|
|
4301
|
-
100% { transform:scale(1); }
|
|
4302
|
-
}
|
|
4303
|
-
@keyframes _osl_tip_fade { from { opacity:0; } to { opacity:1; } }
|
|
4304
|
-
`;
|
|
4305
|
-
class OslTooltipDirective {
|
|
4306
|
-
el;
|
|
4307
|
-
renderer;
|
|
4308
|
-
document;
|
|
4309
|
-
text = '';
|
|
4310
|
-
oslTooltipPosition = 'top';
|
|
4311
|
-
oslTooltipAnimation = 'slide';
|
|
4312
|
-
oslTooltipDisabled = false;
|
|
4313
|
-
oslTooltipMaxWidth = '280px';
|
|
4314
|
-
tooltipEl = null;
|
|
4315
|
-
static cssInjected = false;
|
|
4316
|
-
constructor(el, renderer, document) {
|
|
4317
|
-
this.el = el;
|
|
4318
|
-
this.renderer = renderer;
|
|
4319
|
-
this.document = document;
|
|
4320
|
-
this.injectCss();
|
|
4321
|
-
}
|
|
4322
|
-
show() {
|
|
4323
|
-
if (this.oslTooltipDisabled || !this.text?.trim())
|
|
4324
|
-
return;
|
|
4325
|
-
this.create();
|
|
4326
|
-
this.position();
|
|
4327
|
-
}
|
|
4328
|
-
hide() {
|
|
4329
|
-
this.destroy();
|
|
4330
|
-
}
|
|
4331
|
-
create() {
|
|
4332
|
-
this.destroy();
|
|
4333
|
-
const tip = this.renderer.createElement('div');
|
|
4334
|
-
this.renderer.addClass(tip, 'osl-tooltip');
|
|
4335
|
-
this.renderer.addClass(tip, `osl-tooltip--${this.oslTooltipPosition}`);
|
|
4336
|
-
if (this.oslTooltipAnimation !== 'slide') {
|
|
4337
|
-
this.renderer.addClass(tip, `osl-tooltip--anim-${this.oslTooltipAnimation}`);
|
|
4338
|
-
}
|
|
4339
|
-
this.renderer.setStyle(tip, 'max-width', this.oslTooltipMaxWidth);
|
|
4340
|
-
this.renderer.appendChild(tip, this.renderer.createText(this.text));
|
|
4341
|
-
this.renderer.appendChild(this.document.body, tip);
|
|
4342
|
-
this.tooltipEl = tip;
|
|
4343
|
-
}
|
|
4344
|
-
position() {
|
|
4345
|
-
if (!this.tooltipEl)
|
|
4346
|
-
return;
|
|
4347
|
-
const host = this.el.nativeElement.getBoundingClientRect();
|
|
4348
|
-
const tip = this.tooltipEl.getBoundingClientRect();
|
|
4349
|
-
const gap = 8;
|
|
4350
|
-
let top;
|
|
4351
|
-
let left;
|
|
4352
|
-
switch (this.oslTooltipPosition) {
|
|
4353
|
-
case 'bottom':
|
|
4354
|
-
top = host.bottom + gap;
|
|
4355
|
-
left = host.left + host.width / 2 - tip.width / 2;
|
|
4356
|
-
break;
|
|
4357
|
-
case 'left':
|
|
4358
|
-
top = host.top + host.height / 2 - tip.height / 2;
|
|
4359
|
-
left = host.left - tip.width - gap;
|
|
4360
|
-
break;
|
|
4361
|
-
case 'right':
|
|
4362
|
-
top = host.top + host.height / 2 - tip.height / 2;
|
|
4363
|
-
left = host.right + gap;
|
|
4364
|
-
break;
|
|
4365
|
-
default: // top
|
|
4366
|
-
top = host.top - tip.height - gap;
|
|
4367
|
-
left = host.left + host.width / 2 - tip.width / 2;
|
|
4368
|
-
}
|
|
4369
|
-
left = Math.max(8, Math.min(left, this.document.defaultView.innerWidth - tip.width - 8));
|
|
4370
|
-
top = Math.max(8, top);
|
|
4371
|
-
this.renderer.setStyle(this.tooltipEl, 'top', `${top}px`);
|
|
4372
|
-
this.renderer.setStyle(this.tooltipEl, 'left', `${left}px`);
|
|
4373
|
-
}
|
|
4374
|
-
destroy() {
|
|
4375
|
-
if (this.tooltipEl) {
|
|
4376
|
-
if (this.document.body.contains(this.tooltipEl)) {
|
|
4377
|
-
this.renderer.removeChild(this.document.body, this.tooltipEl);
|
|
4378
|
-
}
|
|
4379
|
-
this.tooltipEl = null;
|
|
4380
|
-
}
|
|
4381
|
-
}
|
|
4382
|
-
injectCss() {
|
|
4383
|
-
if (OslTooltipDirective.cssInjected || this.document.getElementById('_osl_tooltip_css')) {
|
|
4384
|
-
OslTooltipDirective.cssInjected = true;
|
|
4385
|
-
return;
|
|
4386
|
-
}
|
|
4387
|
-
const style = this.document.createElement('style');
|
|
4388
|
-
style.id = '_osl_tooltip_css';
|
|
4389
|
-
style.textContent = TOOLTIP_CSS;
|
|
4390
|
-
this.document.head.appendChild(style);
|
|
4391
|
-
OslTooltipDirective.cssInjected = true;
|
|
4392
|
-
}
|
|
4393
|
-
ngOnDestroy() {
|
|
4394
|
-
this.destroy();
|
|
4395
|
-
}
|
|
4396
|
-
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 });
|
|
4397
|
-
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 });
|
|
4398
|
-
}
|
|
4399
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: OslTooltipDirective, decorators: [{
|
|
4400
|
-
type: Directive,
|
|
4401
|
-
args: [{ selector: '[oslTooltip]', standalone: true }]
|
|
4402
|
-
}], ctorParameters: () => [{ type: i0.ElementRef }, { type: i0.Renderer2 }, { type: Document, decorators: [{
|
|
4403
|
-
type: Inject,
|
|
4404
|
-
args: [DOCUMENT]
|
|
4405
|
-
}] }], propDecorators: { text: [{
|
|
4406
|
-
type: Input,
|
|
4407
|
-
args: ['oslTooltip']
|
|
4408
|
-
}], oslTooltipPosition: [{
|
|
4409
|
-
type: Input
|
|
4410
|
-
}], oslTooltipAnimation: [{
|
|
4411
|
-
type: Input
|
|
4412
|
-
}], oslTooltipDisabled: [{
|
|
4413
|
-
type: Input
|
|
4414
|
-
}], oslTooltipMaxWidth: [{
|
|
4415
|
-
type: Input
|
|
4416
|
-
}], show: [{
|
|
4417
|
-
type: HostListener,
|
|
4418
|
-
args: ['mouseenter']
|
|
4419
|
-
}], hide: [{
|
|
4420
|
-
type: HostListener,
|
|
4421
|
-
args: ['mouseleave']
|
|
4422
|
-
}] } });
|
|
4423
|
-
|
|
4424
4338
|
// ─── Component ────────────────────────────────────────────────────────────────
|
|
4425
4339
|
class OslReportGrid {
|
|
4426
4340
|
// Inputs
|
|
@@ -6033,7 +5947,6 @@ class FormStructureModule {
|
|
|
6033
5947
|
OslTooltipDirective,
|
|
6034
5948
|
MatDatepickerModule,
|
|
6035
5949
|
MatMenuModule,
|
|
6036
|
-
ScrollingModule,
|
|
6037
5950
|
DragDropModule,
|
|
6038
5951
|
MatTooltipModule,
|
|
6039
5952
|
OverlayModule,
|
|
@@ -6065,7 +5978,6 @@ class FormStructureModule {
|
|
|
6065
5978
|
OslSkeletonModule,
|
|
6066
5979
|
MatDatepickerModule,
|
|
6067
5980
|
MatMenuModule,
|
|
6068
|
-
ScrollingModule,
|
|
6069
5981
|
DragDropModule,
|
|
6070
5982
|
MatTooltipModule,
|
|
6071
5983
|
OverlayModule,
|
|
@@ -6119,7 +6031,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
6119
6031
|
OslTooltipDirective,
|
|
6120
6032
|
MatDatepickerModule,
|
|
6121
6033
|
MatMenuModule,
|
|
6122
|
-
ScrollingModule,
|
|
6123
6034
|
DragDropModule,
|
|
6124
6035
|
MatTooltipModule,
|
|
6125
6036
|
OverlayModule,
|