osl-base-extended 1.1.24 → 1.1.26

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.
@@ -1,11 +1,11 @@
1
1
  import * as i0 from '@angular/core';
2
- import { Component, Inject, inject, Injector, Injectable, InjectionToken, PLATFORM_ID, Input, Optional, Directive, EventEmitter, Output, HostListener, ViewChild, NgModule } from '@angular/core';
2
+ import { Component, Inject, inject, Injector, Injectable, InjectionToken, PLATFORM_ID, Input, Optional, Directive, EventEmitter, Output, HostListener, ViewChild, NgModule, ChangeDetectorRef, ChangeDetectionStrategy } from '@angular/core';
3
3
  import * as i1 from '@angular/material/dialog';
4
4
  import { MAT_DIALOG_DATA, MatDialogModule, MatDialog } from '@angular/material/dialog';
5
5
  import { MatSnackBar } from '@angular/material/snack-bar';
6
6
  import { Router } from '@angular/router';
7
7
  import * as i1$2 from '@angular/common';
8
- import { NgTemplateOutlet, NgComponentOutlet, isPlatformBrowser, NgStyle, DatePipe } from '@angular/common';
8
+ import { NgTemplateOutlet, NgComponentOutlet, isPlatformBrowser, DatePipe, DecimalPipe, NgStyle, NgClass, UpperCasePipe } from '@angular/common';
9
9
  import * as i3 from '@angular/material/button';
10
10
  import { MatIconButton, MatButtonModule } from '@angular/material/button';
11
11
  import * as i2 from '@angular/material/icon';
@@ -21,6 +21,9 @@ import { MatDatepickerModule } from '@angular/material/datepicker';
21
21
  import { MatInputModule } from '@angular/material/input';
22
22
  import { debounceTime as debounceTime$1, distinctUntilChanged as distinctUntilChanged$1 } from 'rxjs/operators';
23
23
  import { MatMenuModule } from '@angular/material/menu';
24
+ import { ScrollingModule } from '@angular/cdk/scrolling';
25
+ import * as i3$1 from '@angular/cdk/drag-drop';
26
+ import { moveItemInArray, DragDropModule } from '@angular/cdk/drag-drop';
24
27
 
25
28
  class OslBaseExtended {
26
29
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: OslBaseExtended, deps: [], target: i0.ɵɵFactoryTarget.Component });
@@ -170,7 +173,7 @@ class baseComponent {
170
173
  continue;
171
174
  }
172
175
  if (el.inputType === 'email' && !isEmpty) {
173
- const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
176
+ const emailRegex = /^(?!.*\.\.)(?!.*\.$)[A-Za-z0-9][A-Za-z0-9._%+-]{0,63}@[A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)*\.[A-Za-z]{2,}$/;
174
177
  if (!emailRegex.test(value)) {
175
178
  errors.push(`${el.label} must be a valid email address`);
176
179
  }
@@ -960,6 +963,7 @@ class Oslinput {
960
963
  skeletonLoading = false;
961
964
  skeletonTheme = 'light';
962
965
  onlyChars = false;
966
+ isCapitalize = false;
963
967
  /** Enforce N decimal places. Shows 0.00 when blank; auto-pads on blur. */
964
968
  decimalPortion = null;
965
969
  modelChange = new EventEmitter();
@@ -1102,6 +1106,9 @@ class Oslinput {
1102
1106
  this.modelChange.emit(this.model);
1103
1107
  return;
1104
1108
  }
1109
+ if (this.isCapitalize) {
1110
+ value = typeof value == 'string' ? value?.toUpperCase() : value;
1111
+ }
1105
1112
  const processed = this.mask ? this.applyMask(value) : value;
1106
1113
  this.model = processed;
1107
1114
  this.modelChange.emit(this.model);
@@ -1141,7 +1148,7 @@ class Oslinput {
1141
1148
  return result;
1142
1149
  }
1143
1150
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: Oslinput, deps: [], target: i0.ɵɵFactoryTarget.Component });
1144
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: Oslinput, isStandalone: false, selector: "osl-input", inputs: { label: "label", required: "required", disabled: "disabled", model: "model", type: "type", placeholder: "placeholder", mask: "mask", min: "min", max: "max", minLength: "minLength", maxLength: "maxLength", prefixIcon: "prefixIcon", suffixIcon: "suffixIcon", skeletonLoading: "skeletonLoading", skeletonTheme: "skeletonTheme", onlyChars: "onlyChars", decimalPortion: "decimalPortion" }, outputs: { modelChange: "modelChange", changeEv: "changeEv" }, usesOnChanges: true, ngImport: i0, template: "<div class=\"d-flex flex-column\">\r\n @if(label){\r\n <div [oslSkeleton]=\"skeletonLoading\" [oslSkeletonTheme]=\"skeletonTheme\">\r\n <label class=\"label\" [class.txt-clr-red]=\"myField.touched && myField.invalid\">\r\n <span [oslSkeleton]=\"skeletonLoading\" [oslSkeletonTheme]=\"skeletonTheme\">{{label}} <span class=\"txt-clr-red\">{{required?'*':''}}</span></span>\r\n </label>\r\n </div>\r\n}\r\n\r\n <div class=\"input-wrapper\" [oslSkeleton]=\"skeletonLoading\" [oslSkeletonTheme]=\"skeletonTheme\" [class.plain]=\"!hasWrapper\" [class.error]=\"myField.touched && myField.invalid\" [class.input-disabled]=\"disabled\">\r\n @if(prefixIcon) {\r\n <mat-icon class=\"input-icon prefix-icon\">{{prefixIcon}}</mat-icon>\r\n }\r\n <input\r\n\r\n [type]=\"inputType\"\r\n [ngModel]=\"model\"\r\n (ngModelChange)=\"onModelChange($event)\"\r\n (keydown)=\"onKeyDown($event)\"\r\n (focus)=\"onFocusIn($event)\"\r\n (blur)=\"onFocusOut()\"\r\n (change)=\"onChange()\"\r\n [required]=\"required\"\r\n [disabled]=\"disabled\"\r\n [placeholder]=\"placeholder\"\r\n [attr.min]=\"min || null\"\r\n [attr.max]=\"max || null\"\r\n [attr.minlength]=\"minLength\"\r\n [attr.maxlength]=\"maxLength\"\r\n [email]=\"type === 'email'\"\r\n #myField=\"ngModel\"\r\n class=\"inner-input\"\r\n >\r\n @if(type === 'password') {\r\n <button type=\"button\" class=\"password-toggle\" (click)=\"togglePassword()\" tabindex=\"-1\">\r\n <mat-icon>{{showPassword ? 'visibility_off' : 'visibility'}}</mat-icon>\r\n </button>\r\n }\r\n @if(suffixIcon && type !== 'password') {\r\n <mat-icon class=\"input-icon suffix-icon\">{{suffixIcon}}</mat-icon>\r\n }\r\n </div>\r\n\r\n @if(myField.touched && myField.invalid) {\r\n @if(myField.errors?.['required']) {\r\n <mat-hint class=\"hint\">{{label}} is Required!</mat-hint>\r\n } @else if(myField.errors?.['email']) {\r\n <mat-hint class=\"hint\">Please enter a valid email address.</mat-hint>\r\n } @else if(myField.errors?.['minlength']) {\r\n <mat-hint class=\"hint\">Minimum {{minLength}} characters required.</mat-hint>\r\n } @else if(myField.errors?.['maxlength']) {\r\n <mat-hint class=\"hint\">Maximum {{maxLength}} characters allowed.</mat-hint>\r\n } @else if(myField.errors?.['min']) {\r\n <mat-hint class=\"hint\">Value must be at least {{min}}.</mat-hint>\r\n } @else if(myField.errors?.['max']) {\r\n <mat-hint class=\"hint\">Value must be at most {{max}}.</mat-hint>\r\n }\r\n }\r\n</div>\r\n", styles: [".label{font-size:var(--osl-label-font-size);margin-bottom:5px}.input-wrapper{display:flex;align-items:center;height:var(--osl-control-height);width:100%;border-radius:var(--osl-border-radius);border:1px solid var(--osl-border-color);padding:0 8px;gap:6px;transition:border-color .5s}.input-wrapper:focus-within{border-color:var(--osl-focus-border-color)}.input-wrapper.error{border-color:var(--osl-error-color)}.input-wrapper.input-disabled{background:#f5f5f5;opacity:.7;cursor:not-allowed}.input-wrapper.plain{padding:0;border:none;height:auto;gap:0}.input-wrapper.plain .inner-input{border:1px solid var(--osl-border-color);border-radius:var(--osl-border-radius);padding:5px;height:var(--osl-control-height)}.input-wrapper.plain .inner-input:focus{border-color:var(--osl-focus-border-color)}.input-wrapper.plain .inner-input.error{border-color:var(--osl-error-color)}.inner-input{flex:1;min-width:0;height:100%;border:none;outline:none;font-size:var(--osl-text-font-size);background:transparent;width:100%}.inner-input:disabled{cursor:not-allowed;background:transparent}.inner-input::placeholder{font-size:var(--osl-label-font-size);color:#aaa}.input-icon{font-size:18px;width:18px;height:18px;color:#888;flex-shrink:0;-webkit-user-select:none;user-select:none}.password-toggle{display:flex;align-items:center;justify-content:center;background:none;border:none;padding:0;cursor:pointer;color:#888;flex-shrink:0}.password-toggle mat-icon{font-size:18px;width:18px;height:18px}.password-toggle:hover{color:#333}.hint{color:var(--osl-error-color);margin-top:2px;font-size:var(--osl-hint-font-size)}.txt-clr-red{color:var(--osl-error-color)}\n"], dependencies: [{ kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1$1.EmailValidator, selector: "[email][formControlName],[email][formControl],[email][ngModel]", inputs: ["email"] }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i2$1.MatHint, selector: "mat-hint", inputs: ["align", "id"] }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "directive", type: OslSkeletonDirective, selector: "[oslSkeleton]", inputs: ["oslSkeleton", "oslSkeletonType", "oslSkeletonAnimation", "oslSkeletonTheme", "oslSkeletonColor", "oslSkeletonHighlight", "oslSkeletonRadius", "oslSkeletonRows", "oslSkeletonRowGap", "oslSkeletonZIndex", "oslSkeletonDelay", "oslSkeletonDuration", "oslSkeletonMinHeight", "oslSkeletonForceReread", "oslSkeletonCircleSize", "oslSkeletonListItems", "oslSkeletonTableRows", "oslSkeletonTableCols", "oslSkeletonCardLines", "oslSkeletonBgColor"] }] });
1151
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: Oslinput, isStandalone: false, selector: "osl-input", inputs: { label: "label", required: "required", disabled: "disabled", model: "model", type: "type", placeholder: "placeholder", mask: "mask", min: "min", max: "max", minLength: "minLength", maxLength: "maxLength", prefixIcon: "prefixIcon", suffixIcon: "suffixIcon", skeletonLoading: "skeletonLoading", skeletonTheme: "skeletonTheme", onlyChars: "onlyChars", isCapitalize: "isCapitalize", decimalPortion: "decimalPortion" }, outputs: { modelChange: "modelChange", changeEv: "changeEv" }, usesOnChanges: true, ngImport: i0, template: "<div class=\"d-flex flex-column\">\r\n @if(label){\r\n <div [oslSkeleton]=\"skeletonLoading\" [oslSkeletonTheme]=\"skeletonTheme\">\r\n <label class=\"label\" [class.txt-clr-red]=\"myField.touched && myField.invalid\">\r\n <span [oslSkeleton]=\"skeletonLoading\" [oslSkeletonTheme]=\"skeletonTheme\">{{label}} <span class=\"txt-clr-red\">{{required?'*':''}}</span></span>\r\n </label>\r\n </div>\r\n}\r\n\r\n <div class=\"input-wrapper\" [oslSkeleton]=\"skeletonLoading\" [oslSkeletonTheme]=\"skeletonTheme\" [class.plain]=\"!hasWrapper\" [class.error]=\"myField.touched && myField.invalid\" [class.input-disabled]=\"disabled\">\r\n @if(prefixIcon) {\r\n <mat-icon class=\"input-icon prefix-icon\">{{prefixIcon}}</mat-icon>\r\n }\r\n <input\r\n\r\n [type]=\"inputType\"\r\n [ngModel]=\"model\"\r\n (ngModelChange)=\"onModelChange($event)\"\r\n (keydown)=\"onKeyDown($event)\"\r\n (focus)=\"onFocusIn($event)\"\r\n (blur)=\"onFocusOut()\"\r\n (change)=\"onChange()\"\r\n [required]=\"required\"\r\n [disabled]=\"disabled\"\r\n [placeholder]=\"placeholder\"\r\n [attr.min]=\"min || null\"\r\n [attr.max]=\"max || null\"\r\n [attr.minlength]=\"minLength\"\r\n [attr.maxlength]=\"maxLength\"\r\n [email]=\"type === 'email'\"\r\n #myField=\"ngModel\"\r\n class=\"inner-input\"\r\n >\r\n @if(type === 'password') {\r\n <button type=\"button\" class=\"password-toggle\" (click)=\"togglePassword()\" tabindex=\"-1\">\r\n <mat-icon>{{showPassword ? 'visibility_off' : 'visibility'}}</mat-icon>\r\n </button>\r\n }\r\n @if(suffixIcon && type !== 'password') {\r\n <mat-icon class=\"input-icon suffix-icon\">{{suffixIcon}}</mat-icon>\r\n }\r\n </div>\r\n\r\n @if(myField.touched && myField.invalid) {\r\n @if(myField.errors?.['required']) {\r\n <mat-hint class=\"hint\">{{label}} is Required!</mat-hint>\r\n } @else if(myField.errors?.['email']) {\r\n <mat-hint class=\"hint\">Please enter a valid email address.</mat-hint>\r\n } @else if(myField.errors?.['minlength']) {\r\n <mat-hint class=\"hint\">Minimum {{minLength}} characters required.</mat-hint>\r\n } @else if(myField.errors?.['maxlength']) {\r\n <mat-hint class=\"hint\">Maximum {{maxLength}} characters allowed.</mat-hint>\r\n } @else if(myField.errors?.['min']) {\r\n <mat-hint class=\"hint\">Value must be at least {{min}}.</mat-hint>\r\n } @else if(myField.errors?.['max']) {\r\n <mat-hint class=\"hint\">Value must be at most {{max}}.</mat-hint>\r\n }\r\n }\r\n</div>\r\n", styles: [".label{font-size:var(--osl-label-font-size);margin-bottom:5px}.input-wrapper{display:flex;align-items:center;height:var(--osl-control-height);width:100%;border-radius:var(--osl-border-radius);border:1px solid var(--osl-border-color);padding:0 8px;gap:6px;transition:border-color .5s}.input-wrapper:focus-within{border-color:var(--osl-focus-border-color)}.input-wrapper.error{border-color:var(--osl-error-color)}.input-wrapper.input-disabled{background:#f5f5f5;opacity:.7;cursor:not-allowed}.input-wrapper.plain{padding:0;border:none;height:auto;gap:0}.input-wrapper.plain .inner-input{border:1px solid var(--osl-border-color);border-radius:var(--osl-border-radius);padding:5px;height:var(--osl-control-height)}.input-wrapper.plain .inner-input:focus{border-color:var(--osl-focus-border-color)}.input-wrapper.plain .inner-input.error{border-color:var(--osl-error-color)}.inner-input{flex:1;min-width:0;height:100%;border:none;outline:none;font-size:var(--osl-text-font-size);background:transparent;width:100%}.inner-input:disabled{cursor:not-allowed;background:transparent}.inner-input::placeholder{font-size:var(--osl-label-font-size);color:#aaa}.input-icon{font-size:18px;width:18px;height:18px;color:#888;flex-shrink:0;-webkit-user-select:none;user-select:none}.password-toggle{display:flex;align-items:center;justify-content:center;background:none;border:none;padding:0;cursor:pointer;color:#888;flex-shrink:0}.password-toggle mat-icon{font-size:18px;width:18px;height:18px}.password-toggle:hover{color:#333}.hint{color:var(--osl-error-color);margin-top:2px;font-size:var(--osl-hint-font-size)}.txt-clr-red{color:var(--osl-error-color)}\n"], dependencies: [{ kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1$1.EmailValidator, selector: "[email][formControlName],[email][formControl],[email][ngModel]", inputs: ["email"] }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i2$1.MatHint, selector: "mat-hint", inputs: ["align", "id"] }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "directive", type: OslSkeletonDirective, selector: "[oslSkeleton]", inputs: ["oslSkeleton", "oslSkeletonType", "oslSkeletonAnimation", "oslSkeletonTheme", "oslSkeletonColor", "oslSkeletonHighlight", "oslSkeletonRadius", "oslSkeletonRows", "oslSkeletonRowGap", "oslSkeletonZIndex", "oslSkeletonDelay", "oslSkeletonDuration", "oslSkeletonMinHeight", "oslSkeletonForceReread", "oslSkeletonCircleSize", "oslSkeletonListItems", "oslSkeletonTableRows", "oslSkeletonTableCols", "oslSkeletonCardLines", "oslSkeletonBgColor"] }] });
1145
1152
  }
1146
1153
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: Oslinput, decorators: [{
1147
1154
  type: Component,
@@ -1194,6 +1201,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
1194
1201
  }], onlyChars: [{
1195
1202
  type: Input,
1196
1203
  args: ['onlyChars']
1204
+ }], isCapitalize: [{
1205
+ type: Input,
1206
+ args: ['isCapitalize']
1197
1207
  }], decimalPortion: [{
1198
1208
  type: Input,
1199
1209
  args: ['decimalPortion']
@@ -2180,11 +2190,11 @@ class DynamicForm {
2180
2190
  elem.change(this.model, undefined, selectedObj);
2181
2191
  }
2182
2192
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: DynamicForm, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
2183
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: DynamicForm, isStandalone: false, selector: "osl-dynamic-form", inputs: { elements: "elements", model: "model", skeletonLoading: "skeletonLoading", skeletonTheme: "skeletonTheme" }, outputs: { modelChange: "modelChange" }, usesOnChanges: true, ngImport: i0, template: "@if(elements && elements.length > 0) {\r\n <div class=\"row align-items-center w-100\">\r\n @for(elem of elements; track elem) {\r\n @if(!(elem.hideIf ? elem.hideIf(model) : elem.hide)) {\r\n <ng-container *ngTemplateOutlet=\"formField; context: { $implicit: elem }\"></ng-container>\r\n }\r\n }\r\n </div>\r\n}\r\n\r\n<ng-template #formField let-elem>\r\n @if(elem.hideIf ? !elem.hideIf(model) : true) {\r\n @switch (elem.elementType) {\r\n\r\n @case (\"textbox\") {\r\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\">\r\n <osl-input\r\n [skeletonLoading]=\"skeletonLoading\"\r\n [skeletonTheme]=\"skeletonTheme\"\r\n [label]=\"elem.label\"\r\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\r\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\r\n [type]=\"elem.inputType || 'text'\"\r\n [placeholder]=\"elem.placeholder || ''\"\r\n [mask]=\"elem.mask || ''\"\r\n [min]=\"elem.min ?? ''\"\r\n [max]=\"elem.max ?? ''\"\r\n [minLength]=\"elem.minLength ?? null\"\r\n [maxLength]=\"elem.maxLength ?? null\"\r\n [prefixIcon]=\"elem.prefixIcon || ''\"\r\n [suffixIcon]=\"elem.suffixIcon || ''\"\r\n [onlyChars]=\"!!elem.onlyChars\"\r\n [decimalPortion]=\"elem.decimalPortion ?? null\"\r\n (changeEv)=\"elem.change ? elem.change(model) : null\"\r\n [(model)]=\"model[elem.key]\"\r\n ></osl-input>\r\n </div>\r\n }\r\n\r\n @case (\"textarea\") {\r\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\">\r\n <osl-textarea\r\n [label]=\"elem.label\"\r\n [skeletonLoading]=\"skeletonLoading\"\r\n [skeletonTheme]=\"skeletonTheme\"\r\n\r\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\r\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\r\n [rows]=\"elem.textareaRows || 3\"\r\n [placeholder]=\"elem.placeholder || ''\"\r\n [maxLength]=\"elem.maxLength ?? null\"\r\n [minLength]=\"elem.minLength ?? null\"\r\n [characterCounter]=\"!!elem.characterCounter\"\r\n [resize]=\"elem.resize || 'none'\"\r\n (changeEv)=\"elem.change ? elem.change(model) : null\"\r\n [(model)]=\"model[elem.key]\"\r\n ></osl-textarea>\r\n </div>\r\n }\r\n\r\n @case (\"select\") {\r\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\">\r\n <osl-select\r\n [label]=\"elem.label\"\r\n [skeletonLoading]=\"skeletonLoading\"\r\n [skeletonTheme]=\"skeletonTheme\"\r\n\r\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\r\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\r\n [datasource]=\"elem.datasource || []\"\r\n [displayField]=\"elem.displayField || ''\"\r\n [valueField]=\"elem.valueField || ''\"\r\n [placeholder]=\"elem.selectPlaceholder || 'Select...'\"\r\n [loading]=\"elem.loadingIf ? elem.loadingIf(model) : false\"\r\n [clearable]=\"!!elem.clearable\"\r\n (changeEv)=\"elem.change ? onSelectChange(elem, $event) : null\"\r\n [(model)]=\"model[elem.key]\"\r\n ></osl-select>\r\n </div>\r\n }\r\n\r\n @case (\"radio\") {\r\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\">\r\n <osl-radio\r\n [label]=\"elem.label\"\r\n [skeletonLoading]=\"skeletonLoading\"\r\n [skeletonTheme]=\"skeletonTheme\"\r\n\r\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\r\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\r\n [datasource]=\"elem.datasource || []\"\r\n [displayField]=\"elem.displayField || ''\"\r\n [valueField]=\"elem.valueField || ''\"\r\n [inline]=\"!!elem.inline\"\r\n (changeEv)=\"elem.change ? elem.change(model) : null\"\r\n [(model)]=\"model[elem.key]\"\r\n ></osl-radio>\r\n </div>\r\n }\r\n\r\n @case (\"slide-toggle\") {\r\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\">\r\n <osl-slide-toggle\r\n [label]=\"elem.label\"\r\n [skeletonLoading]=\"skeletonLoading\"\r\n [skeletonTheme]=\"skeletonTheme\"\r\n\r\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\r\n [labelPosition]=\"elem.labelPosition || 'after'\"\r\n [trueLabel]=\"elem.trueLabel || ''\"\r\n [falseLabel]=\"elem.falseLabel || ''\"\r\n (changeEv)=\"elem.change ? elem.change(model) : null\"\r\n [(model)]=\"model[elem.key]\"\r\n ></osl-slide-toggle>\r\n </div>\r\n }\r\n\r\n @case (\"autocomplete\") {\r\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\">\r\n <osl-autocomplete\r\n [label]=\"elem.label\"\r\n [skeletonLoading]=\"skeletonLoading\"\r\n [skeletonTheme]=\"skeletonTheme\"\r\n\r\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\r\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\r\n [datasource]=\"elem.datasource || []\"\r\n [displayField]=\"elem.displayField || ''\"\r\n [valueField]=\"elem.valueField || ''\"\r\n [placeholder]=\"elem.autocompletePlaceholder || 'Type to search...'\"\r\n [loading]=\"elem.loadingIf ? elem.loadingIf(model) : false\"\r\n (changeEv)=\"elem.change ? onSelectChange(elem, $event) : null\"\r\n [(model)]=\"model[elem.key]\"\r\n [methodName]=\"elem.apiMethod\"\r\n [service]=\"elem.apiService\"\r\n [object]=\"model[elem.objectName]\"\r\n [searchType]=\"elem.searchType\"\r\n [configMethodName]=\"elem.apiConfigMethod\"\r\n [isLister]=\"elem.isListerAutocomplete\"\r\n \r\n \r\n ></osl-autocomplete>\r\n </div>\r\n }\r\n\r\n @case (\"file-uploader\") {\r\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\">\r\n <osl-file-upload\r\n [skeletonLoading]=\"skeletonLoading\"\r\n [skeletonTheme]=\"skeletonTheme\"\r\n\r\n [label]=\"elem.label\"\r\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\r\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\r\n [accept]=\"elem.accept || ''\"\r\n [multiple]=\"!!elem.multiple\"\r\n [maxSize]=\"elem.maxFileSize || 0\"\r\n (changeEv)=\"elem.change ? elem.change(model) : null\"\r\n [(model)]=\"model[elem.key]\"\r\n ></osl-file-upload>\r\n </div>\r\n }\r\n\r\n @case (\"datepicker\") {\r\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\">\r\n <osl-datepicker\r\n [skeletonLoading]=\"skeletonLoading\"\r\n [skeletonTheme]=\"skeletonTheme\"\r\n\r\n [label]=\"elem.label\"\r\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\r\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\r\n [dateType]=\"elem.dateType || 'date'\"\r\n [placeholder]=\"elem.placeholder || ''\"\r\n [minDate]=\"elem.minDate || ''\"\r\n [maxDate]=\"elem.maxDate || ''\"\r\n (changeEv)=\"elem.change ? elem.change(model) : null\"\r\n [(model)]=\"model[elem.key]\"\r\n ></osl-datepicker>\r\n </div>\r\n }\r\n\r\n @case (\"checkbox\") {\r\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\">\r\n <osl-checkbox\r\n [skeletonLoading]=\"skeletonLoading\"\r\n [skeletonTheme]=\"skeletonTheme\"\r\n\r\n [label]=\"elem.label\"\r\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\r\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\r\n [indeterminate]=\"!!elem.indeterminate\"\r\n (changeEv)=\"elem.change ? elem.change(model) : null\"\r\n [(model)]=\"model[elem.key]\"\r\n ></osl-checkbox>\r\n </div>\r\n }\r\n\r\n @case (\"button\") {\r\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\">\r\n <osl-button\r\n \r\n [label]=\"elem.label\"\r\n [loading]=\"elem.loadingIf ? elem.loadingIf(model) : false\"\r\n variant=\"secondary\"\r\n (clickEv)=\"elem.change ? elem.change(model) : null\"\r\n ></osl-button>\r\n </div>\r\n }\r\n\r\n @case (\"fieldset\") {\r\n <div [class]=\"'col-md-'+elem.columns+' mt-3'\">\r\n <fieldset class=\"osl-fieldset\">\r\n @if(elem.label) {\r\n <legend class=\"osl-fieldset-legend\" [oslSkeleton]=\"skeletonLoading\" [oslSkeletonTheme]=\"skeletonTheme\">\r\n <span class=\"osl-fieldset-legend__text\">{{ elem.label }}</span>\r\n </legend>\r\n }\r\n <div class=\"row w-100 osl-fieldset-body\">\r\n @for(innerElem of elem.rows; track innerElem) {\r\n @if(!(innerElem.hideIf ? innerElem.hideIf(model) : innerElem.hide)) {\r\n <ng-container *ngTemplateOutlet=\"formField; context: { $implicit: innerElem }\"></ng-container>\r\n }\r\n }\r\n </div>\r\n </fieldset>\r\n </div>\r\n }\r\n\r\n @case (\"templateRef\"){\r\n <div [class]=\"'col-md-'+elem.columns+' mt-3'\">\r\n <ng-container *ngTemplateOutlet=\"elem.templateRef; context: { $implicit: elem}\"></ng-container>\r\n\r\n \r\n </div>\r\n }\r\n @case (\"spacer\"){\r\n <div [class]=\"'col-md-'+elem.columns+' mt-3'\">\r\n <!-- <ng-container *ngTemplateOutlet=\"elem.templateRef; context: { $implicit: elem}\"></ng-container> -->\r\n\r\n \r\n </div>\r\n }\r\n\r\n }\r\n }\r\n</ng-template>\r\n", styles: [".osl-fieldset{border:1.5px solid var(--osl-border-color, #e5e7eb);border-radius:10px;padding:0 16px 16px;margin:0;background:linear-gradient(135deg,#f9fafb,#fff);box-shadow:0 1px 4px #0000000d;position:relative;transition:box-shadow .2s}.osl-fieldset:focus-within{box-shadow:0 0 0 3px #6366f114,0 2px 8px #0000000f;border-color:var(--osl-primary, #6366f1)}.osl-fieldset-legend{padding:0 6px;margin-left:8px;line-height:1;float:none;width:auto}.osl-fieldset-legend__text{display:inline-flex;align-items:center;gap:6px;font-size:12px;font-weight:600;letter-spacing:.04em;text-transform:uppercase;background:#fff;padding:2px 10px;border:1.5px solid var(--osl-border-color, #e5e7eb);box-shadow:0 1px 3px #6366f11a}.osl-fieldset-body{margin-top:4px}\n"], dependencies: [{ kind: "directive", type: i1$2.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: OslSkeletonDirective, selector: "[oslSkeleton]", inputs: ["oslSkeleton", "oslSkeletonType", "oslSkeletonAnimation", "oslSkeletonTheme", "oslSkeletonColor", "oslSkeletonHighlight", "oslSkeletonRadius", "oslSkeletonRows", "oslSkeletonRowGap", "oslSkeletonZIndex", "oslSkeletonDelay", "oslSkeletonDuration", "oslSkeletonMinHeight", "oslSkeletonForceReread", "oslSkeletonCircleSize", "oslSkeletonListItems", "oslSkeletonTableRows", "oslSkeletonTableCols", "oslSkeletonCardLines", "oslSkeletonBgColor"] }, { kind: "component", type: Oslinput, selector: "osl-input", inputs: ["label", "required", "disabled", "model", "type", "placeholder", "mask", "min", "max", "minLength", "maxLength", "prefixIcon", "suffixIcon", "skeletonLoading", "skeletonTheme", "onlyChars", "decimalPortion"], outputs: ["modelChange", "changeEv"] }, { kind: "component", type: Osltextarea, selector: "osl-textarea", inputs: ["label", "rows", "required", "disabled", "model", "placeholder", "maxLength", "minLength", "characterCounter", "resize", "skeletonLoading", "skeletonTheme"], outputs: ["modelChange", "changeEv"] }, { kind: "component", type: OslSelect, selector: "osl-select", inputs: ["label", "required", "disabled", "model", "datasource", "displayField", "valueField", "placeholder", "loading", "clearable", "skeletonLoading", "skeletonTheme"], outputs: ["modelChange", "changeEv"] }, { kind: "component", type: OslRadio, selector: "osl-radio", inputs: ["label", "required", "disabled", "model", "datasource", "displayField", "valueField", "inline", "skeletonLoading", "skeletonTheme"], outputs: ["modelChange", "changeEv"] }, { kind: "component", type: OslSlideToggle, selector: "osl-slide-toggle", inputs: ["label", "disabled", "model", "labelPosition", "trueLabel", "falseLabel", "skeletonLoading", "skeletonTheme"], outputs: ["modelChange", "changeEv"] }, { kind: "component", type: OslAutocomplete, selector: "osl-autocomplete", inputs: ["label", "required", "disabled", "model", "datasource", "displayField", "valueField", "placeholder", "loading", "searchType", "methodName", "configMethodName", "service", "object", "skeletonLoading", "skeletonTheme", "isLister"], outputs: ["modelChange", "changeEv"] }, { kind: "component", type: OslFileUpload, selector: "osl-file-upload", inputs: ["label", "required", "disabled", "model", "accept", "multiple", "maxSize", "skeletonLoading", "skeletonTheme"], outputs: ["modelChange", "changeEv"] }, { kind: "component", type: OslDatepicker, selector: "osl-datepicker", inputs: ["label", "required", "disabled", "model", "dateType", "placeholder", "minDate", "maxDate", "skeletonLoading", "skeletonTheme"], outputs: ["modelChange", "changeEv"] }, { kind: "component", type: OslCheckbox, selector: "osl-checkbox", inputs: ["label", "disabled", "required", "model", "indeterminate", "skeletonLoading", "skeletonTheme"], outputs: ["modelChange", "changeEv"] }, { kind: "component", type: OslButton, selector: "osl-button", inputs: ["label", "icon", "variant", "size", "disabled", "loading", "type", "fullWidth"], outputs: ["clickEv"] }] });
2193
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: DynamicForm, isStandalone: false, selector: "osl-dynamic-form", inputs: { elements: "elements", model: "model", skeletonLoading: "skeletonLoading", skeletonTheme: "skeletonTheme" }, outputs: { modelChange: "modelChange" }, usesOnChanges: true, ngImport: i0, template: "@if(elements && elements.length > 0) {\r\n <div class=\"row align-items-center w-100\">\r\n @for(elem of elements; track elem) {\r\n @if(!(elem.hideIf ? elem.hideIf(model) : elem.hide)) {\r\n <ng-container *ngTemplateOutlet=\"formField; context: { $implicit: elem }\"></ng-container>\r\n }\r\n }\r\n </div>\r\n}\r\n\r\n<ng-template #formField let-elem>\r\n @if(elem.hideIf ? !elem.hideIf(model) : true) {\r\n @switch (elem.elementType) {\r\n\r\n @case (\"textbox\") {\r\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\">\r\n <osl-input\r\n [skeletonLoading]=\"skeletonLoading\"\r\n [skeletonTheme]=\"skeletonTheme\"\r\n [label]=\"elem.label\"\r\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\r\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\r\n [type]=\"elem.inputType || 'text'\"\r\n [placeholder]=\"elem.placeholder || ''\"\r\n [mask]=\"elem.mask || ''\"\r\n [min]=\"elem.min ?? ''\"\r\n [max]=\"elem.max ?? ''\"\r\n [minLength]=\"elem.minLength ?? null\"\r\n [maxLength]=\"elem.maxLength ?? null\"\r\n [prefixIcon]=\"elem.prefixIcon || ''\"\r\n [suffixIcon]=\"elem.suffixIcon || ''\"\r\n [onlyChars]=\"!!elem.onlyChars\"\r\n [decimalPortion]=\"elem.decimalPortion ?? null\"\r\n (changeEv)=\"elem.change ? elem.change(model) : null\"\r\n [(model)]=\"model[elem.key]\"\r\n [isCapitalize]=\"elem.isCapitalize\"\r\n ></osl-input>\r\n </div>\r\n }\r\n\r\n @case (\"textarea\") {\r\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\">\r\n <osl-textarea\r\n [label]=\"elem.label\"\r\n [skeletonLoading]=\"skeletonLoading\"\r\n [skeletonTheme]=\"skeletonTheme\"\r\n\r\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\r\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\r\n [rows]=\"elem.textareaRows || 3\"\r\n [placeholder]=\"elem.placeholder || ''\"\r\n [maxLength]=\"elem.maxLength ?? null\"\r\n [minLength]=\"elem.minLength ?? null\"\r\n [characterCounter]=\"!!elem.characterCounter\"\r\n [resize]=\"elem.resize || 'none'\"\r\n (changeEv)=\"elem.change ? elem.change(model) : null\"\r\n [(model)]=\"model[elem.key]\"\r\n ></osl-textarea>\r\n </div>\r\n }\r\n\r\n @case (\"select\") {\r\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\">\r\n <osl-select\r\n [label]=\"elem.label\"\r\n [skeletonLoading]=\"skeletonLoading\"\r\n [skeletonTheme]=\"skeletonTheme\"\r\n\r\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\r\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\r\n [datasource]=\"elem.datasource || []\"\r\n [displayField]=\"elem.displayField || ''\"\r\n [valueField]=\"elem.valueField || ''\"\r\n [placeholder]=\"elem.selectPlaceholder || 'Select...'\"\r\n [loading]=\"elem.loadingIf ? elem.loadingIf(model) : false\"\r\n [clearable]=\"!!elem.clearable\"\r\n (changeEv)=\"elem.change ? onSelectChange(elem, $event) : null\"\r\n [(model)]=\"model[elem.key]\"\r\n ></osl-select>\r\n </div>\r\n }\r\n\r\n @case (\"radio\") {\r\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\">\r\n <osl-radio\r\n [label]=\"elem.label\"\r\n [skeletonLoading]=\"skeletonLoading\"\r\n [skeletonTheme]=\"skeletonTheme\"\r\n\r\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\r\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\r\n [datasource]=\"elem.datasource || []\"\r\n [displayField]=\"elem.displayField || ''\"\r\n [valueField]=\"elem.valueField || ''\"\r\n [inline]=\"!!elem.inline\"\r\n (changeEv)=\"elem.change ? elem.change(model) : null\"\r\n [(model)]=\"model[elem.key]\"\r\n ></osl-radio>\r\n </div>\r\n }\r\n\r\n @case (\"slide-toggle\") {\r\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\">\r\n <osl-slide-toggle\r\n [label]=\"elem.label\"\r\n [skeletonLoading]=\"skeletonLoading\"\r\n [skeletonTheme]=\"skeletonTheme\"\r\n\r\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\r\n [labelPosition]=\"elem.labelPosition || 'after'\"\r\n [trueLabel]=\"elem.trueLabel || ''\"\r\n [falseLabel]=\"elem.falseLabel || ''\"\r\n (changeEv)=\"elem.change ? elem.change(model) : null\"\r\n [(model)]=\"model[elem.key]\"\r\n ></osl-slide-toggle>\r\n </div>\r\n }\r\n\r\n @case (\"autocomplete\") {\r\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\">\r\n <osl-autocomplete\r\n [label]=\"elem.label\"\r\n [skeletonLoading]=\"skeletonLoading\"\r\n [skeletonTheme]=\"skeletonTheme\"\r\n\r\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\r\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\r\n [datasource]=\"elem.datasource || []\"\r\n [displayField]=\"elem.displayField || ''\"\r\n [valueField]=\"elem.valueField || ''\"\r\n [placeholder]=\"elem.autocompletePlaceholder || 'Type to search...'\"\r\n [loading]=\"elem.loadingIf ? elem.loadingIf(model) : false\"\r\n (changeEv)=\"elem.change ? onSelectChange(elem, $event) : null\"\r\n [(model)]=\"model[elem.key]\"\r\n [methodName]=\"elem.apiMethod\"\r\n [service]=\"elem.apiService\"\r\n [object]=\"model[elem.objectName]\"\r\n [searchType]=\"elem.searchType\"\r\n [configMethodName]=\"elem.apiConfigMethod\"\r\n [isLister]=\"elem.isListerAutocomplete\"\r\n \r\n \r\n ></osl-autocomplete>\r\n </div>\r\n }\r\n\r\n @case (\"file-uploader\") {\r\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\">\r\n <osl-file-upload\r\n [skeletonLoading]=\"skeletonLoading\"\r\n [skeletonTheme]=\"skeletonTheme\"\r\n\r\n [label]=\"elem.label\"\r\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\r\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\r\n [accept]=\"elem.accept || ''\"\r\n [multiple]=\"!!elem.multiple\"\r\n [maxSize]=\"elem.maxFileSize || 0\"\r\n (changeEv)=\"elem.change ? elem.change(model) : null\"\r\n [(model)]=\"model[elem.key]\"\r\n ></osl-file-upload>\r\n </div>\r\n }\r\n\r\n @case (\"datepicker\") {\r\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\">\r\n <osl-datepicker\r\n [skeletonLoading]=\"skeletonLoading\"\r\n [skeletonTheme]=\"skeletonTheme\"\r\n\r\n [label]=\"elem.label\"\r\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\r\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\r\n [dateType]=\"elem.dateType || 'date'\"\r\n [placeholder]=\"elem.placeholder || ''\"\r\n [minDate]=\"elem.minDate || ''\"\r\n [maxDate]=\"elem.maxDate || ''\"\r\n (changeEv)=\"elem.change ? elem.change(model) : null\"\r\n [(model)]=\"model[elem.key]\"\r\n ></osl-datepicker>\r\n </div>\r\n }\r\n\r\n @case (\"checkbox\") {\r\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\">\r\n <osl-checkbox\r\n [skeletonLoading]=\"skeletonLoading\"\r\n [skeletonTheme]=\"skeletonTheme\"\r\n\r\n [label]=\"elem.label\"\r\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\r\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\r\n [indeterminate]=\"!!elem.indeterminate\"\r\n (changeEv)=\"elem.change ? elem.change(model) : null\"\r\n [(model)]=\"model[elem.key]\"\r\n ></osl-checkbox>\r\n </div>\r\n }\r\n\r\n @case (\"button\") {\r\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\">\r\n <osl-button\r\n \r\n [label]=\"elem.label\"\r\n [loading]=\"elem.loadingIf ? elem.loadingIf(model) : false\"\r\n variant=\"secondary\"\r\n (clickEv)=\"elem.change ? elem.change(model) : null\"\r\n ></osl-button>\r\n </div>\r\n }\r\n\r\n @case (\"fieldset\") {\r\n <div [class]=\"'col-md-'+elem.columns+' mt-3'\">\r\n <fieldset class=\"osl-fieldset\">\r\n @if(elem.label) {\r\n <legend class=\"osl-fieldset-legend\" [oslSkeleton]=\"skeletonLoading\" [oslSkeletonTheme]=\"skeletonTheme\">\r\n <span class=\"osl-fieldset-legend__text\">{{ elem.label }}</span>\r\n </legend>\r\n }\r\n <div class=\"row w-100 osl-fieldset-body\">\r\n @for(innerElem of elem.rows; track innerElem) {\r\n @if(!(innerElem.hideIf ? innerElem.hideIf(model) : innerElem.hide)) {\r\n <ng-container *ngTemplateOutlet=\"formField; context: { $implicit: innerElem }\"></ng-container>\r\n }\r\n }\r\n </div>\r\n </fieldset>\r\n </div>\r\n }\r\n\r\n @case (\"templateRef\"){\r\n <div [class]=\"'col-md-'+elem.columns+' mt-3'\">\r\n <ng-container *ngTemplateOutlet=\"elem.templateRef; context: { $implicit: elem}\"></ng-container>\r\n\r\n \r\n </div>\r\n }\r\n @case (\"spacer\"){\r\n <div [class]=\"'col-md-'+elem.columns+' mt-3'\">\r\n <!-- <ng-container *ngTemplateOutlet=\"elem.templateRef; context: { $implicit: elem}\"></ng-container> -->\r\n\r\n \r\n </div>\r\n }\r\n\r\n }\r\n }\r\n</ng-template>\r\n", styles: [".osl-fieldset{border:1.5px solid var(--osl-border-color, #e5e7eb);border-radius:10px;padding:0 16px 16px;margin:0;background:linear-gradient(135deg,#f9fafb,#fff);box-shadow:0 1px 4px #0000000d;position:relative;transition:box-shadow .2s}.osl-fieldset:focus-within{box-shadow:0 0 0 3px #6366f114,0 2px 8px #0000000f;border-color:var(--osl-primary, #6366f1)}.osl-fieldset-legend{padding:0 6px;margin-left:8px;line-height:1;float:none;width:auto}.osl-fieldset-legend__text{display:inline-flex;align-items:center;gap:6px;font-size:12px;font-weight:600;letter-spacing:.04em;text-transform:uppercase;background:#fff;padding:2px 10px;border:1.5px solid var(--osl-border-color, #e5e7eb);box-shadow:0 1px 3px #6366f11a}.osl-fieldset-body{margin-top:4px}\n"], dependencies: [{ kind: "directive", type: i1$2.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: OslSkeletonDirective, selector: "[oslSkeleton]", inputs: ["oslSkeleton", "oslSkeletonType", "oslSkeletonAnimation", "oslSkeletonTheme", "oslSkeletonColor", "oslSkeletonHighlight", "oslSkeletonRadius", "oslSkeletonRows", "oslSkeletonRowGap", "oslSkeletonZIndex", "oslSkeletonDelay", "oslSkeletonDuration", "oslSkeletonMinHeight", "oslSkeletonForceReread", "oslSkeletonCircleSize", "oslSkeletonListItems", "oslSkeletonTableRows", "oslSkeletonTableCols", "oslSkeletonCardLines", "oslSkeletonBgColor"] }, { kind: "component", type: Oslinput, selector: "osl-input", inputs: ["label", "required", "disabled", "model", "type", "placeholder", "mask", "min", "max", "minLength", "maxLength", "prefixIcon", "suffixIcon", "skeletonLoading", "skeletonTheme", "onlyChars", "isCapitalize", "decimalPortion"], outputs: ["modelChange", "changeEv"] }, { kind: "component", type: Osltextarea, selector: "osl-textarea", inputs: ["label", "rows", "required", "disabled", "model", "placeholder", "maxLength", "minLength", "characterCounter", "resize", "skeletonLoading", "skeletonTheme"], outputs: ["modelChange", "changeEv"] }, { kind: "component", type: OslSelect, selector: "osl-select", inputs: ["label", "required", "disabled", "model", "datasource", "displayField", "valueField", "placeholder", "loading", "clearable", "skeletonLoading", "skeletonTheme"], outputs: ["modelChange", "changeEv"] }, { kind: "component", type: OslRadio, selector: "osl-radio", inputs: ["label", "required", "disabled", "model", "datasource", "displayField", "valueField", "inline", "skeletonLoading", "skeletonTheme"], outputs: ["modelChange", "changeEv"] }, { kind: "component", type: OslSlideToggle, selector: "osl-slide-toggle", inputs: ["label", "disabled", "model", "labelPosition", "trueLabel", "falseLabel", "skeletonLoading", "skeletonTheme"], outputs: ["modelChange", "changeEv"] }, { kind: "component", type: OslAutocomplete, selector: "osl-autocomplete", inputs: ["label", "required", "disabled", "model", "datasource", "displayField", "valueField", "placeholder", "loading", "searchType", "methodName", "configMethodName", "service", "object", "skeletonLoading", "skeletonTheme", "isLister"], outputs: ["modelChange", "changeEv"] }, { kind: "component", type: OslFileUpload, selector: "osl-file-upload", inputs: ["label", "required", "disabled", "model", "accept", "multiple", "maxSize", "skeletonLoading", "skeletonTheme"], outputs: ["modelChange", "changeEv"] }, { kind: "component", type: OslDatepicker, selector: "osl-datepicker", inputs: ["label", "required", "disabled", "model", "dateType", "placeholder", "minDate", "maxDate", "skeletonLoading", "skeletonTheme"], outputs: ["modelChange", "changeEv"] }, { kind: "component", type: OslCheckbox, selector: "osl-checkbox", inputs: ["label", "disabled", "required", "model", "indeterminate", "skeletonLoading", "skeletonTheme"], outputs: ["modelChange", "changeEv"] }, { kind: "component", type: OslButton, selector: "osl-button", inputs: ["label", "icon", "variant", "size", "disabled", "loading", "type", "fullWidth"], outputs: ["clickEv"] }] });
2184
2194
  }
2185
2195
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: DynamicForm, decorators: [{
2186
2196
  type: Component,
2187
- args: [{ selector: 'osl-dynamic-form', standalone: false, template: "@if(elements && elements.length > 0) {\r\n <div class=\"row align-items-center w-100\">\r\n @for(elem of elements; track elem) {\r\n @if(!(elem.hideIf ? elem.hideIf(model) : elem.hide)) {\r\n <ng-container *ngTemplateOutlet=\"formField; context: { $implicit: elem }\"></ng-container>\r\n }\r\n }\r\n </div>\r\n}\r\n\r\n<ng-template #formField let-elem>\r\n @if(elem.hideIf ? !elem.hideIf(model) : true) {\r\n @switch (elem.elementType) {\r\n\r\n @case (\"textbox\") {\r\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\">\r\n <osl-input\r\n [skeletonLoading]=\"skeletonLoading\"\r\n [skeletonTheme]=\"skeletonTheme\"\r\n [label]=\"elem.label\"\r\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\r\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\r\n [type]=\"elem.inputType || 'text'\"\r\n [placeholder]=\"elem.placeholder || ''\"\r\n [mask]=\"elem.mask || ''\"\r\n [min]=\"elem.min ?? ''\"\r\n [max]=\"elem.max ?? ''\"\r\n [minLength]=\"elem.minLength ?? null\"\r\n [maxLength]=\"elem.maxLength ?? null\"\r\n [prefixIcon]=\"elem.prefixIcon || ''\"\r\n [suffixIcon]=\"elem.suffixIcon || ''\"\r\n [onlyChars]=\"!!elem.onlyChars\"\r\n [decimalPortion]=\"elem.decimalPortion ?? null\"\r\n (changeEv)=\"elem.change ? elem.change(model) : null\"\r\n [(model)]=\"model[elem.key]\"\r\n ></osl-input>\r\n </div>\r\n }\r\n\r\n @case (\"textarea\") {\r\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\">\r\n <osl-textarea\r\n [label]=\"elem.label\"\r\n [skeletonLoading]=\"skeletonLoading\"\r\n [skeletonTheme]=\"skeletonTheme\"\r\n\r\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\r\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\r\n [rows]=\"elem.textareaRows || 3\"\r\n [placeholder]=\"elem.placeholder || ''\"\r\n [maxLength]=\"elem.maxLength ?? null\"\r\n [minLength]=\"elem.minLength ?? null\"\r\n [characterCounter]=\"!!elem.characterCounter\"\r\n [resize]=\"elem.resize || 'none'\"\r\n (changeEv)=\"elem.change ? elem.change(model) : null\"\r\n [(model)]=\"model[elem.key]\"\r\n ></osl-textarea>\r\n </div>\r\n }\r\n\r\n @case (\"select\") {\r\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\">\r\n <osl-select\r\n [label]=\"elem.label\"\r\n [skeletonLoading]=\"skeletonLoading\"\r\n [skeletonTheme]=\"skeletonTheme\"\r\n\r\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\r\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\r\n [datasource]=\"elem.datasource || []\"\r\n [displayField]=\"elem.displayField || ''\"\r\n [valueField]=\"elem.valueField || ''\"\r\n [placeholder]=\"elem.selectPlaceholder || 'Select...'\"\r\n [loading]=\"elem.loadingIf ? elem.loadingIf(model) : false\"\r\n [clearable]=\"!!elem.clearable\"\r\n (changeEv)=\"elem.change ? onSelectChange(elem, $event) : null\"\r\n [(model)]=\"model[elem.key]\"\r\n ></osl-select>\r\n </div>\r\n }\r\n\r\n @case (\"radio\") {\r\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\">\r\n <osl-radio\r\n [label]=\"elem.label\"\r\n [skeletonLoading]=\"skeletonLoading\"\r\n [skeletonTheme]=\"skeletonTheme\"\r\n\r\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\r\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\r\n [datasource]=\"elem.datasource || []\"\r\n [displayField]=\"elem.displayField || ''\"\r\n [valueField]=\"elem.valueField || ''\"\r\n [inline]=\"!!elem.inline\"\r\n (changeEv)=\"elem.change ? elem.change(model) : null\"\r\n [(model)]=\"model[elem.key]\"\r\n ></osl-radio>\r\n </div>\r\n }\r\n\r\n @case (\"slide-toggle\") {\r\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\">\r\n <osl-slide-toggle\r\n [label]=\"elem.label\"\r\n [skeletonLoading]=\"skeletonLoading\"\r\n [skeletonTheme]=\"skeletonTheme\"\r\n\r\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\r\n [labelPosition]=\"elem.labelPosition || 'after'\"\r\n [trueLabel]=\"elem.trueLabel || ''\"\r\n [falseLabel]=\"elem.falseLabel || ''\"\r\n (changeEv)=\"elem.change ? elem.change(model) : null\"\r\n [(model)]=\"model[elem.key]\"\r\n ></osl-slide-toggle>\r\n </div>\r\n }\r\n\r\n @case (\"autocomplete\") {\r\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\">\r\n <osl-autocomplete\r\n [label]=\"elem.label\"\r\n [skeletonLoading]=\"skeletonLoading\"\r\n [skeletonTheme]=\"skeletonTheme\"\r\n\r\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\r\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\r\n [datasource]=\"elem.datasource || []\"\r\n [displayField]=\"elem.displayField || ''\"\r\n [valueField]=\"elem.valueField || ''\"\r\n [placeholder]=\"elem.autocompletePlaceholder || 'Type to search...'\"\r\n [loading]=\"elem.loadingIf ? elem.loadingIf(model) : false\"\r\n (changeEv)=\"elem.change ? onSelectChange(elem, $event) : null\"\r\n [(model)]=\"model[elem.key]\"\r\n [methodName]=\"elem.apiMethod\"\r\n [service]=\"elem.apiService\"\r\n [object]=\"model[elem.objectName]\"\r\n [searchType]=\"elem.searchType\"\r\n [configMethodName]=\"elem.apiConfigMethod\"\r\n [isLister]=\"elem.isListerAutocomplete\"\r\n \r\n \r\n ></osl-autocomplete>\r\n </div>\r\n }\r\n\r\n @case (\"file-uploader\") {\r\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\">\r\n <osl-file-upload\r\n [skeletonLoading]=\"skeletonLoading\"\r\n [skeletonTheme]=\"skeletonTheme\"\r\n\r\n [label]=\"elem.label\"\r\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\r\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\r\n [accept]=\"elem.accept || ''\"\r\n [multiple]=\"!!elem.multiple\"\r\n [maxSize]=\"elem.maxFileSize || 0\"\r\n (changeEv)=\"elem.change ? elem.change(model) : null\"\r\n [(model)]=\"model[elem.key]\"\r\n ></osl-file-upload>\r\n </div>\r\n }\r\n\r\n @case (\"datepicker\") {\r\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\">\r\n <osl-datepicker\r\n [skeletonLoading]=\"skeletonLoading\"\r\n [skeletonTheme]=\"skeletonTheme\"\r\n\r\n [label]=\"elem.label\"\r\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\r\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\r\n [dateType]=\"elem.dateType || 'date'\"\r\n [placeholder]=\"elem.placeholder || ''\"\r\n [minDate]=\"elem.minDate || ''\"\r\n [maxDate]=\"elem.maxDate || ''\"\r\n (changeEv)=\"elem.change ? elem.change(model) : null\"\r\n [(model)]=\"model[elem.key]\"\r\n ></osl-datepicker>\r\n </div>\r\n }\r\n\r\n @case (\"checkbox\") {\r\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\">\r\n <osl-checkbox\r\n [skeletonLoading]=\"skeletonLoading\"\r\n [skeletonTheme]=\"skeletonTheme\"\r\n\r\n [label]=\"elem.label\"\r\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\r\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\r\n [indeterminate]=\"!!elem.indeterminate\"\r\n (changeEv)=\"elem.change ? elem.change(model) : null\"\r\n [(model)]=\"model[elem.key]\"\r\n ></osl-checkbox>\r\n </div>\r\n }\r\n\r\n @case (\"button\") {\r\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\">\r\n <osl-button\r\n \r\n [label]=\"elem.label\"\r\n [loading]=\"elem.loadingIf ? elem.loadingIf(model) : false\"\r\n variant=\"secondary\"\r\n (clickEv)=\"elem.change ? elem.change(model) : null\"\r\n ></osl-button>\r\n </div>\r\n }\r\n\r\n @case (\"fieldset\") {\r\n <div [class]=\"'col-md-'+elem.columns+' mt-3'\">\r\n <fieldset class=\"osl-fieldset\">\r\n @if(elem.label) {\r\n <legend class=\"osl-fieldset-legend\" [oslSkeleton]=\"skeletonLoading\" [oslSkeletonTheme]=\"skeletonTheme\">\r\n <span class=\"osl-fieldset-legend__text\">{{ elem.label }}</span>\r\n </legend>\r\n }\r\n <div class=\"row w-100 osl-fieldset-body\">\r\n @for(innerElem of elem.rows; track innerElem) {\r\n @if(!(innerElem.hideIf ? innerElem.hideIf(model) : innerElem.hide)) {\r\n <ng-container *ngTemplateOutlet=\"formField; context: { $implicit: innerElem }\"></ng-container>\r\n }\r\n }\r\n </div>\r\n </fieldset>\r\n </div>\r\n }\r\n\r\n @case (\"templateRef\"){\r\n <div [class]=\"'col-md-'+elem.columns+' mt-3'\">\r\n <ng-container *ngTemplateOutlet=\"elem.templateRef; context: { $implicit: elem}\"></ng-container>\r\n\r\n \r\n </div>\r\n }\r\n @case (\"spacer\"){\r\n <div [class]=\"'col-md-'+elem.columns+' mt-3'\">\r\n <!-- <ng-container *ngTemplateOutlet=\"elem.templateRef; context: { $implicit: elem}\"></ng-container> -->\r\n\r\n \r\n </div>\r\n }\r\n\r\n }\r\n }\r\n</ng-template>\r\n", styles: [".osl-fieldset{border:1.5px solid var(--osl-border-color, #e5e7eb);border-radius:10px;padding:0 16px 16px;margin:0;background:linear-gradient(135deg,#f9fafb,#fff);box-shadow:0 1px 4px #0000000d;position:relative;transition:box-shadow .2s}.osl-fieldset:focus-within{box-shadow:0 0 0 3px #6366f114,0 2px 8px #0000000f;border-color:var(--osl-primary, #6366f1)}.osl-fieldset-legend{padding:0 6px;margin-left:8px;line-height:1;float:none;width:auto}.osl-fieldset-legend__text{display:inline-flex;align-items:center;gap:6px;font-size:12px;font-weight:600;letter-spacing:.04em;text-transform:uppercase;background:#fff;padding:2px 10px;border:1.5px solid var(--osl-border-color, #e5e7eb);box-shadow:0 1px 3px #6366f11a}.osl-fieldset-body{margin-top:4px}\n"] }]
2197
+ args: [{ selector: 'osl-dynamic-form', standalone: false, template: "@if(elements && elements.length > 0) {\r\n <div class=\"row align-items-center w-100\">\r\n @for(elem of elements; track elem) {\r\n @if(!(elem.hideIf ? elem.hideIf(model) : elem.hide)) {\r\n <ng-container *ngTemplateOutlet=\"formField; context: { $implicit: elem }\"></ng-container>\r\n }\r\n }\r\n </div>\r\n}\r\n\r\n<ng-template #formField let-elem>\r\n @if(elem.hideIf ? !elem.hideIf(model) : true) {\r\n @switch (elem.elementType) {\r\n\r\n @case (\"textbox\") {\r\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\">\r\n <osl-input\r\n [skeletonLoading]=\"skeletonLoading\"\r\n [skeletonTheme]=\"skeletonTheme\"\r\n [label]=\"elem.label\"\r\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\r\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\r\n [type]=\"elem.inputType || 'text'\"\r\n [placeholder]=\"elem.placeholder || ''\"\r\n [mask]=\"elem.mask || ''\"\r\n [min]=\"elem.min ?? ''\"\r\n [max]=\"elem.max ?? ''\"\r\n [minLength]=\"elem.minLength ?? null\"\r\n [maxLength]=\"elem.maxLength ?? null\"\r\n [prefixIcon]=\"elem.prefixIcon || ''\"\r\n [suffixIcon]=\"elem.suffixIcon || ''\"\r\n [onlyChars]=\"!!elem.onlyChars\"\r\n [decimalPortion]=\"elem.decimalPortion ?? null\"\r\n (changeEv)=\"elem.change ? elem.change(model) : null\"\r\n [(model)]=\"model[elem.key]\"\r\n [isCapitalize]=\"elem.isCapitalize\"\r\n ></osl-input>\r\n </div>\r\n }\r\n\r\n @case (\"textarea\") {\r\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\">\r\n <osl-textarea\r\n [label]=\"elem.label\"\r\n [skeletonLoading]=\"skeletonLoading\"\r\n [skeletonTheme]=\"skeletonTheme\"\r\n\r\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\r\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\r\n [rows]=\"elem.textareaRows || 3\"\r\n [placeholder]=\"elem.placeholder || ''\"\r\n [maxLength]=\"elem.maxLength ?? null\"\r\n [minLength]=\"elem.minLength ?? null\"\r\n [characterCounter]=\"!!elem.characterCounter\"\r\n [resize]=\"elem.resize || 'none'\"\r\n (changeEv)=\"elem.change ? elem.change(model) : null\"\r\n [(model)]=\"model[elem.key]\"\r\n ></osl-textarea>\r\n </div>\r\n }\r\n\r\n @case (\"select\") {\r\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\">\r\n <osl-select\r\n [label]=\"elem.label\"\r\n [skeletonLoading]=\"skeletonLoading\"\r\n [skeletonTheme]=\"skeletonTheme\"\r\n\r\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\r\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\r\n [datasource]=\"elem.datasource || []\"\r\n [displayField]=\"elem.displayField || ''\"\r\n [valueField]=\"elem.valueField || ''\"\r\n [placeholder]=\"elem.selectPlaceholder || 'Select...'\"\r\n [loading]=\"elem.loadingIf ? elem.loadingIf(model) : false\"\r\n [clearable]=\"!!elem.clearable\"\r\n (changeEv)=\"elem.change ? onSelectChange(elem, $event) : null\"\r\n [(model)]=\"model[elem.key]\"\r\n ></osl-select>\r\n </div>\r\n }\r\n\r\n @case (\"radio\") {\r\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\">\r\n <osl-radio\r\n [label]=\"elem.label\"\r\n [skeletonLoading]=\"skeletonLoading\"\r\n [skeletonTheme]=\"skeletonTheme\"\r\n\r\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\r\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\r\n [datasource]=\"elem.datasource || []\"\r\n [displayField]=\"elem.displayField || ''\"\r\n [valueField]=\"elem.valueField || ''\"\r\n [inline]=\"!!elem.inline\"\r\n (changeEv)=\"elem.change ? elem.change(model) : null\"\r\n [(model)]=\"model[elem.key]\"\r\n ></osl-radio>\r\n </div>\r\n }\r\n\r\n @case (\"slide-toggle\") {\r\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\">\r\n <osl-slide-toggle\r\n [label]=\"elem.label\"\r\n [skeletonLoading]=\"skeletonLoading\"\r\n [skeletonTheme]=\"skeletonTheme\"\r\n\r\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\r\n [labelPosition]=\"elem.labelPosition || 'after'\"\r\n [trueLabel]=\"elem.trueLabel || ''\"\r\n [falseLabel]=\"elem.falseLabel || ''\"\r\n (changeEv)=\"elem.change ? elem.change(model) : null\"\r\n [(model)]=\"model[elem.key]\"\r\n ></osl-slide-toggle>\r\n </div>\r\n }\r\n\r\n @case (\"autocomplete\") {\r\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\">\r\n <osl-autocomplete\r\n [label]=\"elem.label\"\r\n [skeletonLoading]=\"skeletonLoading\"\r\n [skeletonTheme]=\"skeletonTheme\"\r\n\r\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\r\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\r\n [datasource]=\"elem.datasource || []\"\r\n [displayField]=\"elem.displayField || ''\"\r\n [valueField]=\"elem.valueField || ''\"\r\n [placeholder]=\"elem.autocompletePlaceholder || 'Type to search...'\"\r\n [loading]=\"elem.loadingIf ? elem.loadingIf(model) : false\"\r\n (changeEv)=\"elem.change ? onSelectChange(elem, $event) : null\"\r\n [(model)]=\"model[elem.key]\"\r\n [methodName]=\"elem.apiMethod\"\r\n [service]=\"elem.apiService\"\r\n [object]=\"model[elem.objectName]\"\r\n [searchType]=\"elem.searchType\"\r\n [configMethodName]=\"elem.apiConfigMethod\"\r\n [isLister]=\"elem.isListerAutocomplete\"\r\n \r\n \r\n ></osl-autocomplete>\r\n </div>\r\n }\r\n\r\n @case (\"file-uploader\") {\r\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\">\r\n <osl-file-upload\r\n [skeletonLoading]=\"skeletonLoading\"\r\n [skeletonTheme]=\"skeletonTheme\"\r\n\r\n [label]=\"elem.label\"\r\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\r\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\r\n [accept]=\"elem.accept || ''\"\r\n [multiple]=\"!!elem.multiple\"\r\n [maxSize]=\"elem.maxFileSize || 0\"\r\n (changeEv)=\"elem.change ? elem.change(model) : null\"\r\n [(model)]=\"model[elem.key]\"\r\n ></osl-file-upload>\r\n </div>\r\n }\r\n\r\n @case (\"datepicker\") {\r\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\">\r\n <osl-datepicker\r\n [skeletonLoading]=\"skeletonLoading\"\r\n [skeletonTheme]=\"skeletonTheme\"\r\n\r\n [label]=\"elem.label\"\r\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\r\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\r\n [dateType]=\"elem.dateType || 'date'\"\r\n [placeholder]=\"elem.placeholder || ''\"\r\n [minDate]=\"elem.minDate || ''\"\r\n [maxDate]=\"elem.maxDate || ''\"\r\n (changeEv)=\"elem.change ? elem.change(model) : null\"\r\n [(model)]=\"model[elem.key]\"\r\n ></osl-datepicker>\r\n </div>\r\n }\r\n\r\n @case (\"checkbox\") {\r\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\">\r\n <osl-checkbox\r\n [skeletonLoading]=\"skeletonLoading\"\r\n [skeletonTheme]=\"skeletonTheme\"\r\n\r\n [label]=\"elem.label\"\r\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\r\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\r\n [indeterminate]=\"!!elem.indeterminate\"\r\n (changeEv)=\"elem.change ? elem.change(model) : null\"\r\n [(model)]=\"model[elem.key]\"\r\n ></osl-checkbox>\r\n </div>\r\n }\r\n\r\n @case (\"button\") {\r\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\">\r\n <osl-button\r\n \r\n [label]=\"elem.label\"\r\n [loading]=\"elem.loadingIf ? elem.loadingIf(model) : false\"\r\n variant=\"secondary\"\r\n (clickEv)=\"elem.change ? elem.change(model) : null\"\r\n ></osl-button>\r\n </div>\r\n }\r\n\r\n @case (\"fieldset\") {\r\n <div [class]=\"'col-md-'+elem.columns+' mt-3'\">\r\n <fieldset class=\"osl-fieldset\">\r\n @if(elem.label) {\r\n <legend class=\"osl-fieldset-legend\" [oslSkeleton]=\"skeletonLoading\" [oslSkeletonTheme]=\"skeletonTheme\">\r\n <span class=\"osl-fieldset-legend__text\">{{ elem.label }}</span>\r\n </legend>\r\n }\r\n <div class=\"row w-100 osl-fieldset-body\">\r\n @for(innerElem of elem.rows; track innerElem) {\r\n @if(!(innerElem.hideIf ? innerElem.hideIf(model) : innerElem.hide)) {\r\n <ng-container *ngTemplateOutlet=\"formField; context: { $implicit: innerElem }\"></ng-container>\r\n }\r\n }\r\n </div>\r\n </fieldset>\r\n </div>\r\n }\r\n\r\n @case (\"templateRef\"){\r\n <div [class]=\"'col-md-'+elem.columns+' mt-3'\">\r\n <ng-container *ngTemplateOutlet=\"elem.templateRef; context: { $implicit: elem}\"></ng-container>\r\n\r\n \r\n </div>\r\n }\r\n @case (\"spacer\"){\r\n <div [class]=\"'col-md-'+elem.columns+' mt-3'\">\r\n <!-- <ng-container *ngTemplateOutlet=\"elem.templateRef; context: { $implicit: elem}\"></ng-container> -->\r\n\r\n \r\n </div>\r\n }\r\n\r\n }\r\n }\r\n</ng-template>\r\n", styles: [".osl-fieldset{border:1.5px solid var(--osl-border-color, #e5e7eb);border-radius:10px;padding:0 16px 16px;margin:0;background:linear-gradient(135deg,#f9fafb,#fff);box-shadow:0 1px 4px #0000000d;position:relative;transition:box-shadow .2s}.osl-fieldset:focus-within{box-shadow:0 0 0 3px #6366f114,0 2px 8px #0000000f;border-color:var(--osl-primary, #6366f1)}.osl-fieldset-legend{padding:0 6px;margin-left:8px;line-height:1;float:none;width:auto}.osl-fieldset-legend__text{display:inline-flex;align-items:center;gap:6px;font-size:12px;font-weight:600;letter-spacing:.04em;text-transform:uppercase;background:#fff;padding:2px 10px;border:1.5px solid var(--osl-border-color, #e5e7eb);box-shadow:0 1px 3px #6366f11a}.osl-fieldset-body{margin-top:4px}\n"] }]
2188
2198
  }], ctorParameters: () => [{ type: i0.ChangeDetectorRef }], propDecorators: { elements: [{
2189
2199
  type: Input,
2190
2200
  args: ['elements']
@@ -2809,7 +2819,7 @@ class OslFormGrid {
2809
2819
  elem.change(row, i, selectedObj);
2810
2820
  }
2811
2821
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: OslFormGrid, deps: [], target: i0.ɵɵFactoryTarget.Component });
2812
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: OslFormGrid, isStandalone: false, selector: "osl-form-grid", inputs: { columns: "columns", datasource: "datasource", isPaginated: "isPaginated", pageSize: "pageSize", canAdd: "canAdd", canDelete: "canDelete", loading: "loading", tableHeight: "tableHeight", footerColumns: "footerColumns" }, outputs: { datasourceChange: "datasourceChange", rowAdd: "rowAdd", rowDelete: "rowDelete" }, ngImport: i0, template: "<div class=\"osl-fg-wrapper\">\r\n\r\n <div class=\"osl-fg-table-container\" [style.height]=\"tableHeight\">\r\n <table class=\"osl-fg-table\">\r\n\r\n <thead class=\"osl-fg-thead\">\r\n <tr>\r\n <!-- Actions column: + add button in header -->\r\n @if (hasActions) {\r\n <th class=\"osl-fg-th osl-fg-th--actions\">\r\n @if (canAdd) {\r\n <button class=\"osl-grid-icon-btn\" (click)=\"addRow()\" [disabled]=\"loading\" title=\"Add row\">\r\n <mat-icon>add</mat-icon>\r\n </button>\r\n }\r\n </th>\r\n }\r\n @for (col of columns; track col.key) {\r\n <th class=\"osl-fg-th\" [style.width]=\"col.width\">\r\n {{ col.displayName }}\r\n @if (colRequired(col)) {\r\n <span class=\"osl-fg-th-required\">*</span>\r\n }\r\n </th>\r\n }\r\n </tr>\r\n </thead>\r\n\r\n <tbody>\r\n\r\n @if (loading) {\r\n @for (sk of skeletonRows; track $index) {\r\n <tr class=\"osl-fg-row osl-fg-row--skeleton\">\r\n @if (hasActions) {\r\n <td class=\"osl-fg-td osl-fg-td--actions\">\r\n <div class=\"osl-fg-skeleton osl-fg-skeleton--sm\"></div>\r\n </td>\r\n }\r\n @for (col of columns; track col.key) {\r\n <td class=\"osl-fg-td\"><div class=\"osl-fg-skeleton\"></div></td>\r\n }\r\n </tr>\r\n }\r\n\r\n } @else if (pagedData.length === 0) {\r\n <tr>\r\n <td [attr.colspan]=\"columns.length + (hasActions ? 1 : 0)\" class=\"osl-fg-empty\">\r\n <div class=\"osl-fg-empty-inner\">\r\n <svg width=\"40\" height=\"40\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\">\r\n <rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"2\"/>\r\n <line x1=\"3\" y1=\"9\" x2=\"21\" y2=\"9\"/>\r\n <line x1=\"9\" y1=\"9\" x2=\"9\" y2=\"21\"/>\r\n </svg>\r\n <p>No rows yet.{{ canAdd ? ' Use + to get started.' : '' }}</p>\r\n </div>\r\n </td>\r\n </tr>\r\n\r\n } @else {\r\n @for (row of pagedData; track $index; let i = $index) {\r\n <tr class=\"osl-fg-row\">\r\n\r\n <!-- Delete button first -->\r\n @if (hasActions) {\r\n <td class=\"osl-fg-td osl-fg-td--actions\">\r\n @if (canDelete) {\r\n <button class=\"osl-grid-icon-btn osl-grid-icon-btn--danger\" (click)=\"deleteRow(i)\" title=\"Delete\">\r\n <mat-icon>delete_outline</mat-icon>\r\n </button>\r\n }\r\n </td>\r\n }\r\n\r\n @for (col of columns; track col.key) {\r\n <td class=\"osl-fg-td\" [style.width]=\"col.width\">\r\n @if (col.formElem) {\r\n <div class=\"osl-fg-cell-form\">\r\n @switch (col.formElem.elementType) {\r\n\r\n @case ('textbox') {\r\n <osl-input\r\n [label]=\"''\"\r\n [required]=\"false\"\r\n [disabled]=\"isDisabled(col.formElem,row,i)\"\r\n [type]=\"col.formElem.inputType || 'text'\"\r\n [placeholder]=\"col.formElem.placeholder || col.displayName\"\r\n [mask]=\"col.formElem.mask || ''\"\r\n [min]=\"col.formElem.min ?? ''\"\r\n [max]=\"col.formElem.max ?? ''\"\r\n [minLength]=\"col.formElem.minLength ?? null\"\r\n [maxLength]=\"col.formElem.maxLength ?? null\"\r\n [prefixIcon]=\"col.formElem.prefixIcon || ''\"\r\n [suffixIcon]=\"col.formElem.suffixIcon || ''\"\r\n [decimalPortion]=\"col.formElem.decimalPortion ?? null\"\r\n [model]=\"row[col.key]\"\r\n (modelChange)=\"onCellChange(row, col, $event)\"\r\n (changeEv)=\"col.formElem.change ? col.formElem.change(row,i) : null\">\r\n </osl-input>\r\n }\r\n\r\n @case ('textarea') {\r\n <osl-textarea\r\n [label]=\"''\"\r\n [required]=\"false\"\r\n [disabled]=\"isDisabled(col.formElem,row,i)\"\r\n [rows]=\"col.formElem.textareaRows || 3\"\r\n [placeholder]=\"col.formElem.placeholder || col.displayName\"\r\n [maxLength]=\"col.formElem.maxLength ?? null\"\r\n [minLength]=\"col.formElem.minLength ?? null\"\r\n [characterCounter]=\"!!col.formElem.characterCounter\"\r\n [resize]=\"col.formElem.resize || 'none'\"\r\n [model]=\"row[col.key]\"\r\n (modelChange)=\"onCellChange(row, col, $event)\"\r\n (changeEv)=\"col.formElem.change ? col.formElem.change(row,i) : null\">\r\n </osl-textarea>\r\n }\r\n\r\n @case ('select') {\r\n <osl-select\r\n [label]=\"''\"\r\n [required]=\"false\"\r\n [disabled]=\"isDisabled(col.formElem,row,i)\"\r\n [datasource]=\"col.formElem.datasource || []\"\r\n [displayField]=\"col.formElem.displayField || ''\"\r\n [valueField]=\"col.formElem.valueField || ''\"\r\n [placeholder]=\"col.formElem.selectPlaceholder || col.displayName\"\r\n [loading]=\"isLoading(row, col.formElem)\"\r\n [clearable]=\"!!col.formElem.clearable\"\r\n [model]=\"row[col.key]\"\r\n (modelChange)=\"onCellChange(row, col, $event)\"\r\n (changeEv)=\"col.formElem.change ? onSelectChange(col, row, i, $event) : null\">\r\n </osl-select>\r\n }\r\n\r\n @case ('autocomplete') {\r\n <osl-autocomplete\r\n [label]=\"''\"\r\n [required]=\"false\"\r\n [disabled]=\"isDisabled(col.formElem,row,i)\"\r\n [datasource]=\"col.formElem.datasource || []\"\r\n [displayField]=\"col.formElem.displayField || ''\"\r\n [valueField]=\"col.formElem.valueField || ''\"\r\n [placeholder]=\"col.formElem.autocompletePlaceholder || col.displayName\"\r\n [loading]=\"isLoading(row, col.formElem)\"\r\n [searchType]=\"col.formElem.searchType ? col.formElem.searchType : 'Local'\"\r\n [methodName]=\"col.formElem.apiMethod || ''\"\r\n [configMethodName]=\"col.formElem.apiConfigMethod || ''\"\r\n [service]=\"col.formElem.apiService\"\r\n [object]=\"col.formElem.objectName ? row[col.formElem.objectName] : null\"\r\n [isLister]=\"!!col.formElem.isListerAutocomplete\"\r\n [model]=\"row[col.key]\"\r\n (modelChange)=\"onCellChange(row, col, $event)\"\r\n (changeEv)=\"col.formElem.change ? onSelectChange(col, row, i, $event) : null\">\r\n </osl-autocomplete>\r\n }\r\n\r\n @case ('radio') {\r\n <osl-radio\r\n [label]=\"''\"\r\n [required]=\"false\"\r\n [disabled]=\"isDisabled(col.formElem,row,i)\"\r\n [datasource]=\"col.formElem.datasource || []\"\r\n [displayField]=\"col.formElem.displayField || ''\"\r\n [valueField]=\"col.formElem.valueField || ''\"\r\n [inline]=\"!!col.formElem.inline\"\r\n [model]=\"row[col.key]\"\r\n (modelChange)=\"onCellChange(row, col, $event)\"\r\n (changeEv)=\"col.formElem.change ? col.formElem.change(row,i) : null\">\r\n </osl-radio>\r\n }\r\n\r\n @case ('checkbox') {\r\n <osl-checkbox\r\n [label]=\"''\"\r\n [required]=\"false\"\r\n [disabled]=\"isDisabled(col.formElem,row,i)\"\r\n [indeterminate]=\"!!col.formElem.indeterminate\"\r\n [model]=\"row[col.key]\"\r\n (modelChange)=\"onCellChange(row, col, $event)\"\r\n (changeEv)=\"col.formElem.change ? col.formElem.change(row,i) : null\">\r\n </osl-checkbox>\r\n }\r\n\r\n @case ('slide-toggle') {\r\n <osl-slide-toggle\r\n [label]=\"''\"\r\n [disabled]=\"isDisabled(col.formElem,row,i)\"\r\n [labelPosition]=\"col.formElem.labelPosition || 'after'\"\r\n [trueLabel]=\"col.formElem.trueLabel || ''\"\r\n [falseLabel]=\"col.formElem.falseLabel || ''\"\r\n [model]=\"row[col.key]\"\r\n (modelChange)=\"onCellChange(row, col, $event)\"\r\n (changeEv)=\"col.formElem.change ? col.formElem.change(row,i) : null\">\r\n </osl-slide-toggle>\r\n }\r\n\r\n @case ('datepicker') {\r\n <osl-datepicker\r\n [label]=\"''\"\r\n [required]=\"false\"\r\n [disabled]=\"isDisabled(col.formElem,row,i)\"\r\n [dateType]=\"col.formElem.dateType || 'date'\"\r\n [placeholder]=\"col.formElem.placeholder || col.displayName\"\r\n [minDate]=\"col.formElem.minDate || ''\"\r\n [maxDate]=\"col.formElem.maxDate || ''\"\r\n [model]=\"row[col.key]\"\r\n (modelChange)=\"onCellChange(row, col, $event)\"\r\n (changeEv)=\"col.formElem.change ? col.formElem.change(row,i) : null\">\r\n </osl-datepicker>\r\n }\r\n\r\n @case ('file-uploader') {\r\n <osl-file-upload\r\n [label]=\"''\"\r\n [required]=\"false\"\r\n [disabled]=\"isDisabled(col.formElem,row,i)\"\r\n [accept]=\"col.formElem.accept || ''\"\r\n [multiple]=\"!!col.formElem.multiple\"\r\n [maxSize]=\"col.formElem.maxFileSize || 0\"\r\n [model]=\"row[col.key]\"\r\n (modelChange)=\"onCellChange(row, col, $event)\"\r\n (changeEv)=\"col.formElem.change ? col.formElem.change(row,i) : null\">\r\n </osl-file-upload>\r\n }\r\n\r\n }\r\n </div>\r\n } @else {\r\n <span class=\"osl-fg-cell-text\">{{ row[col.key] ?? '--' }}</span>\r\n }\r\n </td>\r\n }\r\n\r\n </tr>\r\n }\r\n }\r\n\r\n </tbody>\r\n\r\n @if (footerColumns && footerColumns.length > 0 && datasource && datasource.length > 0) {\r\n <tfoot class=\"osl-fg-tfoot\">\r\n <tr class=\"osl-fg-footer-row\">\r\n @for (fcol of footerColumns; track $index) {\r\n <td\r\n [class]=\"'osl-fg-footer-td ' + (fcol.class || '')\"\r\n [attr.colspan]=\"fcol.colspan || 1\">\r\n {{ fcol.displayFn ? fcol.displayFn(datasource) : (fcol.display || '') }}\r\n </td>\r\n }\r\n </tr>\r\n </tfoot>\r\n }\r\n\r\n </table>\r\n </div>\r\n\r\n @if (isPaginated) {\r\n <div class=\"osl-fg-pagination\">\r\n\r\n <span class=\"osl-fg-pagination__info\">\r\n @if (loading) {\r\n Loading...\r\n } @else if (_total > 0) {\r\n {{ startRecord }}\u2013{{ endRecord }} of {{ _total }} rows\r\n } @else {\r\n No rows\r\n }\r\n </span>\r\n\r\n <div class=\"osl-fg-pagination__controls\">\r\n <button class=\"osl-fg-page-btn osl-fg-page-btn--nav\" (click)=\"goToPage(1)\" [disabled]=\"currentPage === 1 || loading\">\u00AB</button>\r\n <button class=\"osl-fg-page-btn osl-fg-page-btn--nav\" (click)=\"goToPage(currentPage - 1)\" [disabled]=\"currentPage === 1 || loading\">\u2039 Prev</button>\r\n\r\n @for (page of pageNumbers; track $index) {\r\n @if (page === -1) {\r\n <span class=\"osl-fg-page-ellipsis\">\u2026</span>\r\n } @else {\r\n <button\r\n class=\"osl-fg-page-btn osl-fg-page-btn--num\"\r\n [class.osl-fg-page-btn--active]=\"page === currentPage\"\r\n [disabled]=\"loading\"\r\n (click)=\"goToPage(page)\">\r\n {{ page }}\r\n </button>\r\n }\r\n }\r\n\r\n <button class=\"osl-fg-page-btn osl-fg-page-btn--nav\" (click)=\"goToPage(currentPage + 1)\" [disabled]=\"currentPage === totalPages || loading\">Next \u203A</button>\r\n <button class=\"osl-fg-page-btn osl-fg-page-btn--nav\" (click)=\"goToPage(totalPages)\" [disabled]=\"currentPage === totalPages || loading\">\u00BB</button>\r\n </div>\r\n\r\n <div class=\"osl-fg-pagination__size\">\r\n <select class=\"osl-fg-page-size\" [ngModel]=\"pageSize\" (ngModelChange)=\"onPageSizeChange($event)\" [disabled]=\"loading\">\r\n @for (opt of pageSizeOptions; track opt) {\r\n <option [value]=\"opt\">{{ opt }} per page</option>\r\n }\r\n </select>\r\n </div>\r\n\r\n </div>\r\n }\r\n\r\n</div>\r\n", styles: [".osl-fg-wrapper{display:flex;flex-direction:column;width:100%;font-size:var(--osl-text-font-size);font-family:inherit;border:1px solid var(--osl-border-color, #e5e7eb);border-radius:var(--osl-border-radius, 8px);overflow:hidden;background:#fff}.osl-fg-table-container{overflow-x:auto;overflow-y:auto;min-height:120px}.osl-fg-table{width:100%;border-collapse:collapse;table-layout:auto}.osl-fg-thead{position:sticky;top:0;z-index:2}.osl-fg-th{background:#f3f4f6;color:#4b5563;font-weight:600;font-size:12px;text-transform:uppercase;letter-spacing:.04em;padding:10px 12px;text-align:left;border-bottom:2px solid var(--osl-border-color, #e5e7eb);border-right:1px solid var(--osl-border-color, #e5e7eb);white-space:nowrap;vertical-align:middle}.osl-fg-th:last-child{border-right:none}.osl-fg-th--actions{width:44px;min-width:44px;text-align:center;padding:6px}.osl-fg-th-required{color:#ef4444;margin-left:2px;font-size:14px;line-height:1;vertical-align:middle}.osl-fg-row{border-bottom:1px solid #f0f0f0;transition:background .1s}.osl-fg-row:nth-child(odd){background:#f8fafc}.osl-fg-row:nth-child(2n){background:#fff}.osl-fg-row:hover{background:#eef2ff!important}.osl-fg-row:last-child{border-bottom:none}.osl-fg-row--skeleton{pointer-events:none}.osl-fg-td{padding:4px 10px;vertical-align:middle;border-right:1px solid #f0f0f0}.osl-fg-td:last-child{border-right:none}.osl-fg-td--actions{width:44px;min-width:44px;text-align:center;padding:4px 6px}.osl-fg-cell-form{min-width:100px}.osl-fg-cell-form ::ng-deep .mat-mdc-form-field-subscript-wrapper{display:none!important}.osl-fg-cell-form ::ng-deep .mat-mdc-form-field,.osl-fg-cell-form ::ng-deep mat-form-field{width:100%}.osl-fg-cell-form ::ng-deep .mat-mdc-text-field-wrapper{padding-top:0}.osl-fg-cell-text{color:#111827;font-size:var(--osl-text-font-size)}.osl-fg-add-btn{display:inline-flex;align-items:center;justify-content:center;width:30px;height:30px;border:none;border-radius:6px;background:var(--osl-primary, #6366f1);color:#fff;cursor:pointer;padding:0;box-shadow:0 1px 4px #6366f159;transition:background .15s,box-shadow .15s,transform .1s}.osl-fg-add-btn mat-icon{font-size:18px;width:18px;height:18px;line-height:18px}.osl-fg-add-btn:hover:not(:disabled){background:var(--osl-primary-hover, #4f46e5);box-shadow:0 2px 8px #6366f173;transform:scale(1.05)}.osl-fg-add-btn:disabled{opacity:.4;cursor:not-allowed;transform:none}.osl-fg-delete-btn{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;border:1.5px solid transparent;border-radius:6px;background:transparent;color:#d1d5db;cursor:pointer;padding:0;transition:border-color .15s,color .15s,background .15s}.osl-fg-delete-btn mat-icon{font-size:16px;width:16px;height:16px;line-height:16px}.osl-fg-delete-btn:hover{border-color:#fca5a5;color:#ef4444;background:#fef2f2}.osl-fg-empty{padding:40px 16px;text-align:center}.osl-fg-empty-inner{display:flex;flex-direction:column;align-items:center;gap:10px;color:#9ca3af}.osl-fg-empty-inner svg{opacity:.4}.osl-fg-empty-inner p{margin:0;font-size:13px}@keyframes osl-fg-pulse{0%,to{opacity:1}50%{opacity:.4}}.osl-fg-skeleton{height:36px;border-radius:6px;background:#e5e7eb;animation:osl-fg-pulse 1.4s ease-in-out infinite}.osl-fg-skeleton--sm{height:28px;width:28px;border-radius:4px;margin:0 auto}.osl-fg-tfoot{position:sticky;bottom:0;z-index:2}.osl-fg-footer-td{background:#f3f4f6;color:#4b5563;font-weight:600;font-size:12px;text-transform:uppercase;letter-spacing:.04em;padding:10px 12px;text-align:left;border-top:2px solid var(--osl-border-color, #e5e7eb);border-right:1px solid var(--osl-border-color, #e5e7eb);white-space:nowrap;vertical-align:middle}.osl-fg-footer-td:last-child{border-right:none}.osl-fg-pagination{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:10px 16px;border-top:1px solid var(--osl-border-color, #e5e7eb);background:#f9fafb;flex-shrink:0;flex-wrap:wrap}.osl-fg-pagination__info{font-size:12px;color:#6b7280;white-space:nowrap;min-width:120px}.osl-fg-pagination__controls{display:flex;align-items:center;gap:3px;flex-wrap:nowrap}.osl-fg-pagination__size{display:flex;align-items:center;justify-content:flex-end;min-width:120px}.osl-fg-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-fg-page-btn:hover:not(:disabled){background:#f3f4f6;border-color:#9ca3af}.osl-fg-page-btn:disabled{opacity:.35;cursor:not-allowed}.osl-fg-page-btn--nav{font-size:12px;color:#6b7280}.osl-fg-page-btn--num{min-width:30px;padding:0;font-size:13px}.osl-fg-page-btn--active{background:var(--osl-primary, #6366f1);border-color:var(--osl-primary, #6366f1);color:#fff;font-weight:600}.osl-fg-page-btn--active:hover:not(:disabled){background:var(--osl-primary-hover, #4f46e5);border-color:var(--osl-primary-hover, #4f46e5)}.osl-fg-page-ellipsis{display:inline-flex;align-items:center;justify-content:center;width:30px;height:30px;color:#9ca3af;font-size:13px;pointer-events:none}.osl-fg-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-fg-page-size:focus{border-color:var(--osl-primary, #6366f1)}.osl-fg-page-size:disabled{opacity:.4;cursor:not-allowed}.osl-grid-icon-btn{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;border:1.5px solid var(--osl-border-color);border-radius:var(--osl-border-radius);background:transparent;color:#6b7280;cursor:pointer;padding:0;transition:border-color .15s,color .15s,background .15s;margin:0 2px}.osl-grid-icon-btn mat-icon{font-size:16px;width:16px;height:16px;line-height:16px}.osl-grid-icon-btn:hover{border-color:var(--osl-primary);color:var(--osl-primary);background:#6366f10f}.osl-grid-icon-btn--danger:hover{border-color:#ef4444;color:#ef4444;background:#ef44440f}\n"], dependencies: [{ 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: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: Oslinput, selector: "osl-input", inputs: ["label", "required", "disabled", "model", "type", "placeholder", "mask", "min", "max", "minLength", "maxLength", "prefixIcon", "suffixIcon", "skeletonLoading", "skeletonTheme", "onlyChars", "decimalPortion"], outputs: ["modelChange", "changeEv"] }, { kind: "component", type: Osltextarea, selector: "osl-textarea", inputs: ["label", "rows", "required", "disabled", "model", "placeholder", "maxLength", "minLength", "characterCounter", "resize", "skeletonLoading", "skeletonTheme"], outputs: ["modelChange", "changeEv"] }, { kind: "component", type: OslSelect, selector: "osl-select", inputs: ["label", "required", "disabled", "model", "datasource", "displayField", "valueField", "placeholder", "loading", "clearable", "skeletonLoading", "skeletonTheme"], outputs: ["modelChange", "changeEv"] }, { kind: "component", type: OslRadio, selector: "osl-radio", inputs: ["label", "required", "disabled", "model", "datasource", "displayField", "valueField", "inline", "skeletonLoading", "skeletonTheme"], outputs: ["modelChange", "changeEv"] }, { kind: "component", type: OslSlideToggle, selector: "osl-slide-toggle", inputs: ["label", "disabled", "model", "labelPosition", "trueLabel", "falseLabel", "skeletonLoading", "skeletonTheme"], outputs: ["modelChange", "changeEv"] }, { kind: "component", type: OslAutocomplete, selector: "osl-autocomplete", inputs: ["label", "required", "disabled", "model", "datasource", "displayField", "valueField", "placeholder", "loading", "searchType", "methodName", "configMethodName", "service", "object", "skeletonLoading", "skeletonTheme", "isLister"], outputs: ["modelChange", "changeEv"] }, { kind: "component", type: OslFileUpload, selector: "osl-file-upload", inputs: ["label", "required", "disabled", "model", "accept", "multiple", "maxSize", "skeletonLoading", "skeletonTheme"], outputs: ["modelChange", "changeEv"] }, { kind: "component", type: OslDatepicker, selector: "osl-datepicker", inputs: ["label", "required", "disabled", "model", "dateType", "placeholder", "minDate", "maxDate", "skeletonLoading", "skeletonTheme"], outputs: ["modelChange", "changeEv"] }, { kind: "component", type: OslCheckbox, selector: "osl-checkbox", inputs: ["label", "disabled", "required", "model", "indeterminate", "skeletonLoading", "skeletonTheme"], outputs: ["modelChange", "changeEv"] }] });
2822
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: OslFormGrid, isStandalone: false, selector: "osl-form-grid", inputs: { columns: "columns", datasource: "datasource", isPaginated: "isPaginated", pageSize: "pageSize", canAdd: "canAdd", canDelete: "canDelete", loading: "loading", tableHeight: "tableHeight", footerColumns: "footerColumns" }, outputs: { datasourceChange: "datasourceChange", rowAdd: "rowAdd", rowDelete: "rowDelete" }, ngImport: i0, template: "<div class=\"osl-fg-wrapper\">\r\n\r\n <div class=\"osl-fg-table-container\" [style.height]=\"tableHeight\">\r\n <table class=\"osl-fg-table\">\r\n\r\n <thead class=\"osl-fg-thead\">\r\n <tr>\r\n <!-- Actions column: + add button in header -->\r\n @if (hasActions) {\r\n <th class=\"osl-fg-th osl-fg-th--actions\">\r\n @if (canAdd) {\r\n <button class=\"osl-grid-icon-btn\" (click)=\"addRow()\" [disabled]=\"loading\" title=\"Add row\">\r\n <mat-icon>add</mat-icon>\r\n </button>\r\n }\r\n </th>\r\n }\r\n @for (col of columns; track col.key) {\r\n <th class=\"osl-fg-th\" [style.width]=\"col.width\">\r\n {{ col.displayName }}\r\n @if (colRequired(col)) {\r\n <span class=\"osl-fg-th-required\">*</span>\r\n }\r\n </th>\r\n }\r\n </tr>\r\n </thead>\r\n\r\n <tbody>\r\n\r\n @if (loading) {\r\n @for (sk of skeletonRows; track $index) {\r\n <tr class=\"osl-fg-row osl-fg-row--skeleton\">\r\n @if (hasActions) {\r\n <td class=\"osl-fg-td osl-fg-td--actions\">\r\n <div class=\"osl-fg-skeleton osl-fg-skeleton--sm\"></div>\r\n </td>\r\n }\r\n @for (col of columns; track col.key) {\r\n <td class=\"osl-fg-td\"><div class=\"osl-fg-skeleton\"></div></td>\r\n }\r\n </tr>\r\n }\r\n\r\n } @else if (pagedData.length === 0) {\r\n <tr>\r\n <td [attr.colspan]=\"columns.length + (hasActions ? 1 : 0)\" class=\"osl-fg-empty\">\r\n <div class=\"osl-fg-empty-inner\">\r\n <svg width=\"40\" height=\"40\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\">\r\n <rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"2\"/>\r\n <line x1=\"3\" y1=\"9\" x2=\"21\" y2=\"9\"/>\r\n <line x1=\"9\" y1=\"9\" x2=\"9\" y2=\"21\"/>\r\n </svg>\r\n <p>No rows yet.{{ canAdd ? ' Use + to get started.' : '' }}</p>\r\n </div>\r\n </td>\r\n </tr>\r\n\r\n } @else {\r\n @for (row of pagedData; track $index; let i = $index) {\r\n <tr class=\"osl-fg-row\">\r\n\r\n <!-- Delete button first -->\r\n @if (hasActions) {\r\n <td class=\"osl-fg-td osl-fg-td--actions\">\r\n @if (canDelete) {\r\n <button class=\"osl-grid-icon-btn osl-grid-icon-btn--danger\" (click)=\"deleteRow(i)\" title=\"Delete\">\r\n <mat-icon>delete_outline</mat-icon>\r\n </button>\r\n }\r\n </td>\r\n }\r\n\r\n @for (col of columns; track col.key) {\r\n <td class=\"osl-fg-td\" [style.width]=\"col.width\">\r\n @if (col.formElem) {\r\n <div class=\"osl-fg-cell-form\">\r\n @switch (col.formElem.elementType) {\r\n\r\n @case ('textbox') {\r\n <osl-input\r\n [label]=\"''\"\r\n [required]=\"false\"\r\n [disabled]=\"isDisabled(col.formElem,row,i)\"\r\n [type]=\"col.formElem.inputType || 'text'\"\r\n [placeholder]=\"col.formElem.placeholder || col.displayName\"\r\n [mask]=\"col.formElem.mask || ''\"\r\n [min]=\"col.formElem.min ?? ''\"\r\n [max]=\"col.formElem.max ?? ''\"\r\n [minLength]=\"col.formElem.minLength ?? null\"\r\n [maxLength]=\"col.formElem.maxLength ?? null\"\r\n [prefixIcon]=\"col.formElem.prefixIcon || ''\"\r\n [suffixIcon]=\"col.formElem.suffixIcon || ''\"\r\n [decimalPortion]=\"col.formElem.decimalPortion ?? null\"\r\n [model]=\"row[col.key]\"\r\n (modelChange)=\"onCellChange(row, col, $event)\"\r\n (changeEv)=\"col.formElem.change ? col.formElem.change(row,i) : null\">\r\n </osl-input>\r\n }\r\n\r\n @case ('textarea') {\r\n <osl-textarea\r\n [label]=\"''\"\r\n [required]=\"false\"\r\n [disabled]=\"isDisabled(col.formElem,row,i)\"\r\n [rows]=\"col.formElem.textareaRows || 3\"\r\n [placeholder]=\"col.formElem.placeholder || col.displayName\"\r\n [maxLength]=\"col.formElem.maxLength ?? null\"\r\n [minLength]=\"col.formElem.minLength ?? null\"\r\n [characterCounter]=\"!!col.formElem.characterCounter\"\r\n [resize]=\"col.formElem.resize || 'none'\"\r\n [model]=\"row[col.key]\"\r\n (modelChange)=\"onCellChange(row, col, $event)\"\r\n (changeEv)=\"col.formElem.change ? col.formElem.change(row,i) : null\">\r\n </osl-textarea>\r\n }\r\n\r\n @case ('select') {\r\n <osl-select\r\n [label]=\"''\"\r\n [required]=\"false\"\r\n [disabled]=\"isDisabled(col.formElem,row,i)\"\r\n [datasource]=\"col.formElem.datasource || []\"\r\n [displayField]=\"col.formElem.displayField || ''\"\r\n [valueField]=\"col.formElem.valueField || ''\"\r\n [placeholder]=\"col.formElem.selectPlaceholder || col.displayName\"\r\n [loading]=\"isLoading(row, col.formElem)\"\r\n [clearable]=\"!!col.formElem.clearable\"\r\n [model]=\"row[col.key]\"\r\n (modelChange)=\"onCellChange(row, col, $event)\"\r\n (changeEv)=\"col.formElem.change ? onSelectChange(col, row, i, $event) : null\">\r\n </osl-select>\r\n }\r\n\r\n @case ('autocomplete') {\r\n <osl-autocomplete\r\n [label]=\"''\"\r\n [required]=\"false\"\r\n [disabled]=\"isDisabled(col.formElem,row,i)\"\r\n [datasource]=\"col.formElem.datasource || []\"\r\n [displayField]=\"col.formElem.displayField || ''\"\r\n [valueField]=\"col.formElem.valueField || ''\"\r\n [placeholder]=\"col.formElem.autocompletePlaceholder || col.displayName\"\r\n [loading]=\"isLoading(row, col.formElem)\"\r\n [searchType]=\"col.formElem.searchType ? col.formElem.searchType : 'Local'\"\r\n [methodName]=\"col.formElem.apiMethod || ''\"\r\n [configMethodName]=\"col.formElem.apiConfigMethod || ''\"\r\n [service]=\"col.formElem.apiService\"\r\n [object]=\"col.formElem.objectName ? row[col.formElem.objectName] : null\"\r\n [isLister]=\"!!col.formElem.isListerAutocomplete\"\r\n [model]=\"row[col.key]\"\r\n (modelChange)=\"onCellChange(row, col, $event)\"\r\n (changeEv)=\"col.formElem.change ? onSelectChange(col, row, i, $event) : null\">\r\n </osl-autocomplete>\r\n }\r\n\r\n @case ('radio') {\r\n <osl-radio\r\n [label]=\"''\"\r\n [required]=\"false\"\r\n [disabled]=\"isDisabled(col.formElem,row,i)\"\r\n [datasource]=\"col.formElem.datasource || []\"\r\n [displayField]=\"col.formElem.displayField || ''\"\r\n [valueField]=\"col.formElem.valueField || ''\"\r\n [inline]=\"!!col.formElem.inline\"\r\n [model]=\"row[col.key]\"\r\n (modelChange)=\"onCellChange(row, col, $event)\"\r\n (changeEv)=\"col.formElem.change ? col.formElem.change(row,i) : null\">\r\n </osl-radio>\r\n }\r\n\r\n @case ('checkbox') {\r\n <osl-checkbox\r\n [label]=\"''\"\r\n [required]=\"false\"\r\n [disabled]=\"isDisabled(col.formElem,row,i)\"\r\n [indeterminate]=\"!!col.formElem.indeterminate\"\r\n [model]=\"row[col.key]\"\r\n (modelChange)=\"onCellChange(row, col, $event)\"\r\n (changeEv)=\"col.formElem.change ? col.formElem.change(row,i) : null\">\r\n </osl-checkbox>\r\n }\r\n\r\n @case ('slide-toggle') {\r\n <osl-slide-toggle\r\n [label]=\"''\"\r\n [disabled]=\"isDisabled(col.formElem,row,i)\"\r\n [labelPosition]=\"col.formElem.labelPosition || 'after'\"\r\n [trueLabel]=\"col.formElem.trueLabel || ''\"\r\n [falseLabel]=\"col.formElem.falseLabel || ''\"\r\n [model]=\"row[col.key]\"\r\n (modelChange)=\"onCellChange(row, col, $event)\"\r\n (changeEv)=\"col.formElem.change ? col.formElem.change(row,i) : null\">\r\n </osl-slide-toggle>\r\n }\r\n\r\n @case ('datepicker') {\r\n <osl-datepicker\r\n [label]=\"''\"\r\n [required]=\"false\"\r\n [disabled]=\"isDisabled(col.formElem,row,i)\"\r\n [dateType]=\"col.formElem.dateType || 'date'\"\r\n [placeholder]=\"col.formElem.placeholder || col.displayName\"\r\n [minDate]=\"col.formElem.minDate || ''\"\r\n [maxDate]=\"col.formElem.maxDate || ''\"\r\n [model]=\"row[col.key]\"\r\n (modelChange)=\"onCellChange(row, col, $event)\"\r\n (changeEv)=\"col.formElem.change ? col.formElem.change(row,i) : null\">\r\n </osl-datepicker>\r\n }\r\n\r\n @case ('file-uploader') {\r\n <osl-file-upload\r\n [label]=\"''\"\r\n [required]=\"false\"\r\n [disabled]=\"isDisabled(col.formElem,row,i)\"\r\n [accept]=\"col.formElem.accept || ''\"\r\n [multiple]=\"!!col.formElem.multiple\"\r\n [maxSize]=\"col.formElem.maxFileSize || 0\"\r\n [model]=\"row[col.key]\"\r\n (modelChange)=\"onCellChange(row, col, $event)\"\r\n (changeEv)=\"col.formElem.change ? col.formElem.change(row,i) : null\">\r\n </osl-file-upload>\r\n }\r\n\r\n }\r\n </div>\r\n } @else {\r\n <span class=\"osl-fg-cell-text\">{{ row[col.key] ?? '--' }}</span>\r\n }\r\n </td>\r\n }\r\n\r\n </tr>\r\n }\r\n }\r\n\r\n </tbody>\r\n\r\n @if (footerColumns && footerColumns.length > 0 && datasource && datasource.length > 0) {\r\n <tfoot class=\"osl-fg-tfoot\">\r\n <tr class=\"osl-fg-footer-row\">\r\n @for (fcol of footerColumns; track $index) {\r\n <td\r\n [class]=\"'osl-fg-footer-td ' + (fcol.class || '')\"\r\n [attr.colspan]=\"fcol.colspan || 1\">\r\n {{ fcol.displayFn ? fcol.displayFn(datasource) : (fcol.display || '') }}\r\n </td>\r\n }\r\n </tr>\r\n </tfoot>\r\n }\r\n\r\n </table>\r\n </div>\r\n\r\n @if (isPaginated) {\r\n <div class=\"osl-fg-pagination\">\r\n\r\n <span class=\"osl-fg-pagination__info\">\r\n @if (loading) {\r\n Loading...\r\n } @else if (_total > 0) {\r\n {{ startRecord }}\u2013{{ endRecord }} of {{ _total }} rows\r\n } @else {\r\n No rows\r\n }\r\n </span>\r\n\r\n <div class=\"osl-fg-pagination__controls\">\r\n <button class=\"osl-fg-page-btn osl-fg-page-btn--nav\" (click)=\"goToPage(1)\" [disabled]=\"currentPage === 1 || loading\">\u00AB</button>\r\n <button class=\"osl-fg-page-btn osl-fg-page-btn--nav\" (click)=\"goToPage(currentPage - 1)\" [disabled]=\"currentPage === 1 || loading\">\u2039 Prev</button>\r\n\r\n @for (page of pageNumbers; track $index) {\r\n @if (page === -1) {\r\n <span class=\"osl-fg-page-ellipsis\">\u2026</span>\r\n } @else {\r\n <button\r\n class=\"osl-fg-page-btn osl-fg-page-btn--num\"\r\n [class.osl-fg-page-btn--active]=\"page === currentPage\"\r\n [disabled]=\"loading\"\r\n (click)=\"goToPage(page)\">\r\n {{ page }}\r\n </button>\r\n }\r\n }\r\n\r\n <button class=\"osl-fg-page-btn osl-fg-page-btn--nav\" (click)=\"goToPage(currentPage + 1)\" [disabled]=\"currentPage === totalPages || loading\">Next \u203A</button>\r\n <button class=\"osl-fg-page-btn osl-fg-page-btn--nav\" (click)=\"goToPage(totalPages)\" [disabled]=\"currentPage === totalPages || loading\">\u00BB</button>\r\n </div>\r\n\r\n <div class=\"osl-fg-pagination__size\">\r\n <select class=\"osl-fg-page-size\" [ngModel]=\"pageSize\" (ngModelChange)=\"onPageSizeChange($event)\" [disabled]=\"loading\">\r\n @for (opt of pageSizeOptions; track opt) {\r\n <option [value]=\"opt\">{{ opt }} per page</option>\r\n }\r\n </select>\r\n </div>\r\n\r\n </div>\r\n }\r\n\r\n</div>\r\n", styles: [".osl-fg-wrapper{display:flex;flex-direction:column;width:100%;font-size:var(--osl-text-font-size);font-family:inherit;border:1px solid var(--osl-border-color, #e5e7eb);border-radius:var(--osl-border-radius, 8px);overflow:hidden;background:#fff}.osl-fg-table-container{overflow-x:auto;overflow-y:auto;min-height:120px}.osl-fg-table{width:100%;border-collapse:collapse;table-layout:auto}.osl-fg-thead{position:sticky;top:0;z-index:2}.osl-fg-th{background:#f3f4f6;color:#4b5563;font-weight:600;font-size:12px;text-transform:uppercase;letter-spacing:.04em;padding:10px 12px;text-align:left;border-bottom:2px solid var(--osl-border-color, #e5e7eb);border-right:1px solid var(--osl-border-color, #e5e7eb);white-space:nowrap;vertical-align:middle}.osl-fg-th:last-child{border-right:none}.osl-fg-th--actions{width:44px;min-width:44px;text-align:center;padding:6px}.osl-fg-th-required{color:#ef4444;margin-left:2px;font-size:14px;line-height:1;vertical-align:middle}.osl-fg-row{border-bottom:1px solid #f0f0f0;transition:background .1s}.osl-fg-row:nth-child(odd){background:#f8fafc}.osl-fg-row:nth-child(2n){background:#fff}.osl-fg-row:hover{background:#eef2ff!important}.osl-fg-row:last-child{border-bottom:none}.osl-fg-row--skeleton{pointer-events:none}.osl-fg-td{padding:4px 10px;vertical-align:middle;border-right:1px solid #f0f0f0}.osl-fg-td:last-child{border-right:none}.osl-fg-td--actions{width:44px;min-width:44px;text-align:center;padding:4px 6px}.osl-fg-cell-form{min-width:100px}.osl-fg-cell-form ::ng-deep .mat-mdc-form-field-subscript-wrapper{display:none!important}.osl-fg-cell-form ::ng-deep .mat-mdc-form-field,.osl-fg-cell-form ::ng-deep mat-form-field{width:100%}.osl-fg-cell-form ::ng-deep .mat-mdc-text-field-wrapper{padding-top:0}.osl-fg-cell-text{color:#111827;font-size:var(--osl-text-font-size)}.osl-fg-add-btn{display:inline-flex;align-items:center;justify-content:center;width:30px;height:30px;border:none;border-radius:6px;background:var(--osl-primary, #6366f1);color:#fff;cursor:pointer;padding:0;box-shadow:0 1px 4px #6366f159;transition:background .15s,box-shadow .15s,transform .1s}.osl-fg-add-btn mat-icon{font-size:18px;width:18px;height:18px;line-height:18px}.osl-fg-add-btn:hover:not(:disabled){background:var(--osl-primary-hover, #4f46e5);box-shadow:0 2px 8px #6366f173;transform:scale(1.05)}.osl-fg-add-btn:disabled{opacity:.4;cursor:not-allowed;transform:none}.osl-fg-delete-btn{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;border:1.5px solid transparent;border-radius:6px;background:transparent;color:#d1d5db;cursor:pointer;padding:0;transition:border-color .15s,color .15s,background .15s}.osl-fg-delete-btn mat-icon{font-size:16px;width:16px;height:16px;line-height:16px}.osl-fg-delete-btn:hover{border-color:#fca5a5;color:#ef4444;background:#fef2f2}.osl-fg-empty{padding:40px 16px;text-align:center}.osl-fg-empty-inner{display:flex;flex-direction:column;align-items:center;gap:10px;color:#9ca3af}.osl-fg-empty-inner svg{opacity:.4}.osl-fg-empty-inner p{margin:0;font-size:13px}@keyframes osl-fg-pulse{0%,to{opacity:1}50%{opacity:.4}}.osl-fg-skeleton{height:36px;border-radius:6px;background:#e5e7eb;animation:osl-fg-pulse 1.4s ease-in-out infinite}.osl-fg-skeleton--sm{height:28px;width:28px;border-radius:4px;margin:0 auto}.osl-fg-tfoot{position:sticky;bottom:0;z-index:2}.osl-fg-footer-td{background:#f3f4f6;color:#4b5563;font-weight:600;font-size:12px;text-transform:uppercase;letter-spacing:.04em;padding:10px 12px;text-align:left;border-top:2px solid var(--osl-border-color, #e5e7eb);border-right:1px solid var(--osl-border-color, #e5e7eb);white-space:nowrap;vertical-align:middle}.osl-fg-footer-td:last-child{border-right:none}.osl-fg-pagination{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:10px 16px;border-top:1px solid var(--osl-border-color, #e5e7eb);background:#f9fafb;flex-shrink:0;flex-wrap:wrap}.osl-fg-pagination__info{font-size:12px;color:#6b7280;white-space:nowrap;min-width:120px}.osl-fg-pagination__controls{display:flex;align-items:center;gap:3px;flex-wrap:nowrap}.osl-fg-pagination__size{display:flex;align-items:center;justify-content:flex-end;min-width:120px}.osl-fg-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-fg-page-btn:hover:not(:disabled){background:#f3f4f6;border-color:#9ca3af}.osl-fg-page-btn:disabled{opacity:.35;cursor:not-allowed}.osl-fg-page-btn--nav{font-size:12px;color:#6b7280}.osl-fg-page-btn--num{min-width:30px;padding:0;font-size:13px}.osl-fg-page-btn--active{background:var(--osl-primary, #6366f1);border-color:var(--osl-primary, #6366f1);color:#fff;font-weight:600}.osl-fg-page-btn--active:hover:not(:disabled){background:var(--osl-primary-hover, #4f46e5);border-color:var(--osl-primary-hover, #4f46e5)}.osl-fg-page-ellipsis{display:inline-flex;align-items:center;justify-content:center;width:30px;height:30px;color:#9ca3af;font-size:13px;pointer-events:none}.osl-fg-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-fg-page-size:focus{border-color:var(--osl-primary, #6366f1)}.osl-fg-page-size:disabled{opacity:.4;cursor:not-allowed}.osl-grid-icon-btn{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;border:1.5px solid var(--osl-border-color);border-radius:var(--osl-border-radius);background:transparent;color:#6b7280;cursor:pointer;padding:0;transition:border-color .15s,color .15s,background .15s;margin:0 2px}.osl-grid-icon-btn mat-icon{font-size:16px;width:16px;height:16px;line-height:16px}.osl-grid-icon-btn:hover{border-color:var(--osl-primary);color:var(--osl-primary);background:#6366f10f}.osl-grid-icon-btn--danger:hover{border-color:#ef4444;color:#ef4444;background:#ef44440f}\n"], dependencies: [{ 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: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: Oslinput, selector: "osl-input", inputs: ["label", "required", "disabled", "model", "type", "placeholder", "mask", "min", "max", "minLength", "maxLength", "prefixIcon", "suffixIcon", "skeletonLoading", "skeletonTheme", "onlyChars", "isCapitalize", "decimalPortion"], outputs: ["modelChange", "changeEv"] }, { kind: "component", type: Osltextarea, selector: "osl-textarea", inputs: ["label", "rows", "required", "disabled", "model", "placeholder", "maxLength", "minLength", "characterCounter", "resize", "skeletonLoading", "skeletonTheme"], outputs: ["modelChange", "changeEv"] }, { kind: "component", type: OslSelect, selector: "osl-select", inputs: ["label", "required", "disabled", "model", "datasource", "displayField", "valueField", "placeholder", "loading", "clearable", "skeletonLoading", "skeletonTheme"], outputs: ["modelChange", "changeEv"] }, { kind: "component", type: OslRadio, selector: "osl-radio", inputs: ["label", "required", "disabled", "model", "datasource", "displayField", "valueField", "inline", "skeletonLoading", "skeletonTheme"], outputs: ["modelChange", "changeEv"] }, { kind: "component", type: OslSlideToggle, selector: "osl-slide-toggle", inputs: ["label", "disabled", "model", "labelPosition", "trueLabel", "falseLabel", "skeletonLoading", "skeletonTheme"], outputs: ["modelChange", "changeEv"] }, { kind: "component", type: OslAutocomplete, selector: "osl-autocomplete", inputs: ["label", "required", "disabled", "model", "datasource", "displayField", "valueField", "placeholder", "loading", "searchType", "methodName", "configMethodName", "service", "object", "skeletonLoading", "skeletonTheme", "isLister"], outputs: ["modelChange", "changeEv"] }, { kind: "component", type: OslFileUpload, selector: "osl-file-upload", inputs: ["label", "required", "disabled", "model", "accept", "multiple", "maxSize", "skeletonLoading", "skeletonTheme"], outputs: ["modelChange", "changeEv"] }, { kind: "component", type: OslDatepicker, selector: "osl-datepicker", inputs: ["label", "required", "disabled", "model", "dateType", "placeholder", "minDate", "maxDate", "skeletonLoading", "skeletonTheme"], outputs: ["modelChange", "changeEv"] }, { kind: "component", type: OslCheckbox, selector: "osl-checkbox", inputs: ["label", "disabled", "required", "model", "indeterminate", "skeletonLoading", "skeletonTheme"], outputs: ["modelChange", "changeEv"] }] });
2813
2823
  }
2814
2824
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: OslFormGrid, decorators: [{
2815
2825
  type: Component,
@@ -2925,6 +2935,819 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
2925
2935
  }]
2926
2936
  }] });
2927
2937
 
2938
+ // ─── Component ────────────────────────────────────────────────────────────────
2939
+ class OslReportGrid {
2940
+ // Inputs
2941
+ columns = [];
2942
+ datasource = [];
2943
+ loading = false;
2944
+ totalRecords = 0;
2945
+ autoMode = true;
2946
+ isPaginated = true;
2947
+ pageSize = 50;
2948
+ tableHeight = 'calc(100vh - 220px)';
2949
+ striped = true;
2950
+ exportable = true;
2951
+ rowHeight = 40;
2952
+ rowSelection = null;
2953
+ showAggregates = false;
2954
+ title = '';
2955
+ // Outputs
2956
+ pageChange = new EventEmitter();
2957
+ pageSizeChange = new EventEmitter();
2958
+ sortChange = new EventEmitter();
2959
+ rowClick = new EventEmitter();
2960
+ selectionChange = new EventEmitter();
2961
+ headerScrollRef;
2962
+ bodyScrollRef;
2963
+ cdr = inject(ChangeDetectorRef);
2964
+ datePipe = inject(DatePipe);
2965
+ decimalPipe = inject(DecimalPipe);
2966
+ // ── Column state ─────────────────────────────────────────────────────────────
2967
+ _cols = [];
2968
+ _visibleCols = [];
2969
+ _totalWidth = 0;
2970
+ get visibleCols() { return this._visibleCols; }
2971
+ get totalWidth() { return this._totalWidth; }
2972
+ get pinnedWidth() {
2973
+ return this._visibleCols.filter(c => c._pinned).reduce((s, c) => s + c._width, 0);
2974
+ }
2975
+ updateVisibleCols() {
2976
+ this._visibleCols = this._cols.filter(c => !c._hidden);
2977
+ this._totalWidth = this._visibleCols.reduce((s, c) => s + c._width, 0)
2978
+ + (this.rowSelection === 'multiple' ? 36 : 0);
2979
+ }
2980
+ // ── Sort state ────────────────────────────────────────────────────────────────
2981
+ sortStates = new Map();
2982
+ sortCounter = 0;
2983
+ // ── Filter / Search state ─────────────────────────────────────────────────────
2984
+ globalSearch = '';
2985
+ columnSearch = {};
2986
+ excelFilters = {};
2987
+ excelFilterState = {};
2988
+ openFilterKey = null;
2989
+ filterDropdownPos = { top: 0, left: 0 };
2990
+ // ── Group state ───────────────────────────────────────────────────────────────
2991
+ activeGroups = [];
2992
+ collapsedPaths = new Set();
2993
+ showGroupPanel = true;
2994
+ // ── Flat rows (computed) ──────────────────────────────────────────────────────
2995
+ flatRows = [];
2996
+ _filteredTotal = 0;
2997
+ // ── Pagination ────────────────────────────────────────────────────────────────
2998
+ currentPage = 1;
2999
+ pageSizeOptions = [25, 50, 100, 200, 500];
3000
+ // ── UI state ──────────────────────────────────────────────────────────────────
3001
+ showColumnConfig = false;
3002
+ selectedRows = new Set();
3003
+ columnMenuKey = null;
3004
+ columnMenuPos = { top: 0, left: 0 };
3005
+ copiedCell = null;
3006
+ resizing = null;
3007
+ _suppressNextHeaderClick = false;
3008
+ _columnDragJustEnded = false;
3009
+ // ─── Lifecycle ────────────────────────────────────────────────────────────────
3010
+ ngOnChanges(changes) {
3011
+ if (changes['columns']) {
3012
+ this.initColumns();
3013
+ this.excelFilterState = {};
3014
+ }
3015
+ if (changes['datasource'] || changes['columns']) {
3016
+ this.currentPage = 1;
3017
+ this.processData();
3018
+ }
3019
+ }
3020
+ initColumns() {
3021
+ this._cols = this.columns.map(col => ({
3022
+ ...col,
3023
+ _width: col.width ?? 150,
3024
+ _hidden: col.hidden ?? false,
3025
+ _pinned: col.pinned ?? false,
3026
+ _stickyLeft: 0,
3027
+ sortable: col.sortable !== false,
3028
+ filterable: col.filterable !== false,
3029
+ searchable: col.searchable !== false,
3030
+ groupable: col.groupable !== false,
3031
+ resizable: col.resizable !== false,
3032
+ minWidth: col.minWidth ?? 60,
3033
+ align: col.align ?? 'left',
3034
+ }));
3035
+ this.recomputePinnedOffsets();
3036
+ this.updateVisibleCols();
3037
+ }
3038
+ recomputePinnedOffsets() {
3039
+ let offset = this.rowSelection === 'multiple' ? 36 : 0;
3040
+ for (const col of this._cols) {
3041
+ if (!col._hidden && col._pinned) {
3042
+ col._stickyLeft = offset;
3043
+ offset += col._width;
3044
+ }
3045
+ }
3046
+ }
3047
+ // ─── Data pipeline ────────────────────────────────────────────────────────────
3048
+ getFilteredRows() {
3049
+ let rows = [...this.datasource];
3050
+ if (this.globalSearch.trim()) {
3051
+ const term = this.globalSearch.toLowerCase();
3052
+ rows = rows.filter(row => this.visibleCols.some(col => String(this.getCellDisplay(row, col)).toLowerCase().includes(term)));
3053
+ }
3054
+ for (const col of this._cols) {
3055
+ const term = (this.columnSearch[col.key] ?? '').trim().toLowerCase();
3056
+ if (term) {
3057
+ rows = rows.filter(row => String(this.getCellDisplay(row, col)).toLowerCase().includes(term));
3058
+ }
3059
+ }
3060
+ for (const [key, activeSet] of Object.entries(this.excelFilters)) {
3061
+ if (activeSet.size > 0) {
3062
+ rows = rows.filter(row => activeSet.has(row[key]));
3063
+ }
3064
+ }
3065
+ return rows;
3066
+ }
3067
+ processData() {
3068
+ let rows = this.getFilteredRows();
3069
+ if (this.autoMode && this.sortStates.size > 0) {
3070
+ const sorts = [...this.sortStates.values()].sort((a, b) => a.index - b.index);
3071
+ rows.sort((a, b) => {
3072
+ for (const s of sorts) {
3073
+ const cmp = String(a[s.key] ?? '').localeCompare(String(b[s.key] ?? ''), undefined, { numeric: true });
3074
+ if (cmp !== 0)
3075
+ return s.asc ? cmp : -cmp;
3076
+ }
3077
+ return 0;
3078
+ });
3079
+ }
3080
+ this._filteredTotal = rows.length;
3081
+ if (this.activeGroups.length > 0) {
3082
+ // Group ALL filtered rows first, then paginate the flat list so every
3083
+ // group is computed across the full dataset, not just one page's slice.
3084
+ const allGrouped = this.flattenGroups(rows, this.activeGroups, 0, '');
3085
+ if (this.isPaginated && this.autoMode) {
3086
+ const start = (this.currentPage - 1) * this.pageSize;
3087
+ this.flatRows = allGrouped.slice(start, start + this.pageSize);
3088
+ }
3089
+ else {
3090
+ this.flatRows = allGrouped;
3091
+ }
3092
+ }
3093
+ else if (this.isPaginated && this.autoMode) {
3094
+ const start = (this.currentPage - 1) * this.pageSize;
3095
+ this.flatRows = rows.slice(start, start + this.pageSize)
3096
+ .map((r, i) => ({ _type: 'data', _data: r, _rowIndex: i }));
3097
+ }
3098
+ else {
3099
+ this.flatRows = rows.map((r, i) => ({ _type: 'data', _data: r, _rowIndex: i }));
3100
+ }
3101
+ this.cdr.markForCheck();
3102
+ }
3103
+ flattenGroups(rows, groupKeys, level, parentPath) {
3104
+ const key = groupKeys[0];
3105
+ const remaining = groupKeys.slice(1);
3106
+ const groups = new Map();
3107
+ for (const row of rows) {
3108
+ const val = row[key];
3109
+ if (!groups.has(val))
3110
+ groups.set(val, []);
3111
+ groups.get(val).push(row);
3112
+ }
3113
+ const col = this._cols.find(c => c.key === key);
3114
+ const result = [];
3115
+ for (const [val, groupRows] of groups) {
3116
+ const path = parentPath ? `${parentPath}||${key}=${val}` : `${key}=${val}`;
3117
+ const expanded = !this.collapsedPaths.has(path);
3118
+ result.push({
3119
+ _type: 'group', _key: key, _value: val,
3120
+ _label: this.getCellDisplayByKey(val, col, undefined),
3121
+ _colLabel: col?.label ?? key,
3122
+ _level: level, _count: groupRows.length,
3123
+ _expanded: expanded, _path: path,
3124
+ });
3125
+ if (expanded) {
3126
+ if (remaining.length > 0) {
3127
+ result.push(...this.flattenGroups(groupRows, remaining, level + 1, path));
3128
+ }
3129
+ else {
3130
+ groupRows.forEach((r, i) => result.push({ _type: 'data', _data: r, _rowIndex: i }));
3131
+ }
3132
+ }
3133
+ }
3134
+ return result;
3135
+ }
3136
+ // ─── Sort ─────────────────────────────────────────────────────────────────────
3137
+ onHeaderClick(col, event) {
3138
+ if (!col.sortable)
3139
+ return;
3140
+ if (this._suppressNextHeaderClick || this._columnDragJustEnded)
3141
+ return;
3142
+ const existing = this.sortStates.get(col.key);
3143
+ if (event.shiftKey) {
3144
+ if (existing) {
3145
+ existing.asc ? this.sortStates.set(col.key, { ...existing, asc: false })
3146
+ : this.sortStates.delete(col.key);
3147
+ }
3148
+ else {
3149
+ this.sortStates.set(col.key, { key: col.key, asc: true, index: this.sortCounter++ });
3150
+ }
3151
+ }
3152
+ else {
3153
+ this.sortStates.clear();
3154
+ this.sortStates.set(col.key, { key: col.key, asc: existing ? !existing.asc : true, index: 0 });
3155
+ this.sortCounter = 1;
3156
+ }
3157
+ this.sortChange.emit({ sorts: [...this.sortStates.values()].map(s => ({ key: s.key, asc: s.asc })) });
3158
+ this.processData();
3159
+ }
3160
+ getSortState(key) { return this.sortStates.get(key); }
3161
+ clearSort() { this.sortStates.clear(); this.processData(); }
3162
+ // ─── Column search ────────────────────────────────────────────────────────────
3163
+ onColumnSearch(key, value) {
3164
+ this.columnSearch[key] = value;
3165
+ this.currentPage = 1;
3166
+ this.processData();
3167
+ }
3168
+ // ─── Excel filter ─────────────────────────────────────────────────────────────
3169
+ openExcelFilter(key, event) {
3170
+ event.stopPropagation();
3171
+ if (this.openFilterKey === key) {
3172
+ this.openFilterKey = null;
3173
+ return;
3174
+ }
3175
+ if (!this.excelFilterState[key])
3176
+ this.buildExcelFilterItems(key);
3177
+ const rect = event.currentTarget.getBoundingClientRect();
3178
+ this.filterDropdownPos = {
3179
+ top: rect.bottom + 4,
3180
+ left: Math.max(4, Math.min(rect.left, window.innerWidth - 264)),
3181
+ };
3182
+ this.openFilterKey = key;
3183
+ }
3184
+ buildExcelFilterItems(key) {
3185
+ const activeSet = this.excelFilters[key] ?? new Set();
3186
+ const col = this._cols.find(c => c.key === key);
3187
+ const seen = new Map();
3188
+ for (const row of this.datasource) {
3189
+ const val = row[key];
3190
+ if (!seen.has(val))
3191
+ seen.set(val, this.getCellDisplayByKey(val, col, undefined));
3192
+ }
3193
+ this.excelFilterState[key] = {
3194
+ search: '',
3195
+ allItems: [...seen.entries()].map(([value, label]) => ({
3196
+ value, label,
3197
+ checked: activeSet.size === 0 || activeSet.has(value),
3198
+ })),
3199
+ };
3200
+ }
3201
+ getFilteredExcelItems(key) {
3202
+ const state = this.excelFilterState[key];
3203
+ if (!state)
3204
+ return [];
3205
+ const s = state.search.toLowerCase();
3206
+ return s ? state.allItems.filter(i => i.label.toLowerCase().includes(s)) : state.allItems;
3207
+ }
3208
+ isAllExcelChecked(key) {
3209
+ const state = this.excelFilterState[key];
3210
+ return !!state && state.allItems.every(i => i.checked);
3211
+ }
3212
+ toggleAllExcelFilter(key, checked) {
3213
+ this.excelFilterState[key]?.allItems.forEach(i => (i.checked = checked));
3214
+ }
3215
+ applyExcelFilter(key) {
3216
+ const state = this.excelFilterState[key];
3217
+ if (!state)
3218
+ return;
3219
+ const checked = state.allItems.filter(i => i.checked).map(i => i.value);
3220
+ if (checked.length === state.allItems.length) {
3221
+ delete this.excelFilters[key];
3222
+ }
3223
+ else {
3224
+ this.excelFilters[key] = new Set(checked);
3225
+ }
3226
+ this.openFilterKey = null;
3227
+ this.currentPage = 1;
3228
+ this.processData();
3229
+ }
3230
+ clearExcelFilter(key, event) {
3231
+ event?.stopPropagation();
3232
+ delete this.excelFilters[key];
3233
+ if (this.excelFilterState[key])
3234
+ this.excelFilterState[key].allItems.forEach(i => (i.checked = true));
3235
+ this.processData();
3236
+ }
3237
+ hasActiveFilter(key) {
3238
+ return !!this.excelFilters[key] && this.excelFilters[key].size > 0;
3239
+ }
3240
+ clearAllFilters() {
3241
+ this.excelFilters = {};
3242
+ this.excelFilterState = {};
3243
+ this.columnSearch = {};
3244
+ this.globalSearch = '';
3245
+ this.currentPage = 1;
3246
+ this.processData();
3247
+ }
3248
+ get hasAnyFilter() {
3249
+ return !!this.globalSearch.trim()
3250
+ || Object.values(this.columnSearch).some(v => !!v?.trim())
3251
+ || Object.values(this.excelFilters).some(s => s.size > 0);
3252
+ }
3253
+ // ─── Grouping ─────────────────────────────────────────────────────────────────
3254
+ addGroup(key) {
3255
+ if (!this.activeGroups.includes(key)) {
3256
+ this.activeGroups.push(key);
3257
+ this.collapsedPaths.clear();
3258
+ this.processData();
3259
+ }
3260
+ }
3261
+ removeGroup(key) {
3262
+ this.activeGroups = this.activeGroups.filter(k => k !== key);
3263
+ this.collapsedPaths.clear();
3264
+ this.processData();
3265
+ }
3266
+ clearGroups() { this.activeGroups = []; this.collapsedPaths.clear(); this.processData(); }
3267
+ toggleGroup(path) {
3268
+ this.collapsedPaths.has(path) ? this.collapsedPaths.delete(path) : this.collapsedPaths.add(path);
3269
+ this.processData();
3270
+ }
3271
+ expandAll() { this.collapsedPaths.clear(); this.processData(); }
3272
+ collapseAll() {
3273
+ this.flatRows
3274
+ .filter((r) => r._type === 'group' && r._level === 0)
3275
+ .forEach(r => this.collapsedPaths.add(r._path));
3276
+ this.processData();
3277
+ }
3278
+ onGroupPanelDrop(event) {
3279
+ moveItemInArray(this.activeGroups, event.previousIndex, event.currentIndex);
3280
+ this.collapsedPaths.clear();
3281
+ this.processData();
3282
+ }
3283
+ getGroupColLabel(key) {
3284
+ return this._cols.find(c => c.key === key)?.label ?? key;
3285
+ }
3286
+ // ─── Column config ────────────────────────────────────────────────────────────
3287
+ toggleColumnVisibility(col) {
3288
+ col._hidden = !col._hidden;
3289
+ this.recomputePinnedOffsets();
3290
+ this.updateVisibleCols();
3291
+ this.processData();
3292
+ }
3293
+ toggleColumnPin(col) {
3294
+ col._pinned = !col._pinned;
3295
+ const pinned = this._cols.filter(c => c._pinned);
3296
+ const unpinned = this._cols.filter(c => !c._pinned);
3297
+ this._cols = [...pinned, ...unpinned];
3298
+ this.recomputePinnedOffsets();
3299
+ this.updateVisibleCols();
3300
+ this.closeColumnMenu();
3301
+ this.cdr.markForCheck();
3302
+ }
3303
+ // Header drag: CDK indices are relative to visibleCols DOM order
3304
+ onColumnReorder(event) {
3305
+ if (event.previousIndex === event.currentIndex)
3306
+ return;
3307
+ const fromCol = this._visibleCols[event.previousIndex];
3308
+ const toCol = this._visibleCols[event.currentIndex];
3309
+ if (!fromCol || !toCol)
3310
+ return;
3311
+ const fromIdx = this._cols.indexOf(fromCol);
3312
+ const toIdx = this._cols.indexOf(toCol);
3313
+ if (fromIdx === -1 || toIdx === -1)
3314
+ return;
3315
+ moveItemInArray(this._cols, fromIdx, toIdx);
3316
+ this.recomputePinnedOffsets();
3317
+ this.updateVisibleCols();
3318
+ this.cdr.markForCheck();
3319
+ }
3320
+ // Config panel drag: CDK indices are relative to _cols directly
3321
+ onConfigColumnReorder(event) {
3322
+ if (event.previousIndex === event.currentIndex)
3323
+ return;
3324
+ moveItemInArray(this._cols, event.previousIndex, event.currentIndex);
3325
+ this.recomputePinnedOffsets();
3326
+ this.updateVisibleCols();
3327
+ this.cdr.markForCheck();
3328
+ }
3329
+ autoSizeColumn(col, event) {
3330
+ event?.stopPropagation();
3331
+ const labelPx = col.label.length * 8 + 48;
3332
+ const sample = this.datasource.slice(0, 200);
3333
+ const maxDataPx = sample.reduce((max, row) => {
3334
+ const len = String(this.getCellDisplay(row, col)).length * 7 + 24;
3335
+ return Math.max(max, len);
3336
+ }, 0);
3337
+ col._width = Math.max(col.minWidth ?? 60, Math.min(Math.max(labelPx, maxDataPx), 420));
3338
+ this.recomputePinnedOffsets();
3339
+ this.updateVisibleCols();
3340
+ this.closeColumnMenu();
3341
+ this.cdr.markForCheck();
3342
+ }
3343
+ autoSizeAll() { this.visibleCols.forEach(c => this.autoSizeColumn(c)); }
3344
+ // ─── Column menu ──────────────────────────────────────────────────────────────
3345
+ openColumnMenu(key, event) {
3346
+ event.stopPropagation();
3347
+ this.columnMenuKey = this.columnMenuKey === key ? null : key;
3348
+ if (this.columnMenuKey) {
3349
+ const rect = event.currentTarget.getBoundingClientRect();
3350
+ this.columnMenuPos = { top: rect.bottom + 4, left: Math.max(rect.left - 100, 4) };
3351
+ }
3352
+ }
3353
+ closeColumnMenu() { this.columnMenuKey = null; }
3354
+ getColumnByKey(key) {
3355
+ return this._cols.find(c => c.key === key);
3356
+ }
3357
+ // ─── Column resize ────────────────────────────────────────────────────────────
3358
+ startResize(col, event) {
3359
+ event.stopPropagation();
3360
+ event.preventDefault();
3361
+ this.resizing = { key: col.key, startX: event.clientX, startWidth: col._width };
3362
+ }
3363
+ onMouseMove(event) {
3364
+ if (!this.resizing)
3365
+ return;
3366
+ const col = this._cols.find(c => c.key === this.resizing.key);
3367
+ if (!col)
3368
+ return;
3369
+ col._width = Math.max(col.minWidth ?? 60, this.resizing.startWidth + (event.clientX - this.resizing.startX));
3370
+ this.recomputePinnedOffsets();
3371
+ this._totalWidth = this._visibleCols.reduce((s, c) => s + c._width, 0)
3372
+ + (this.rowSelection === 'multiple' ? 36 : 0);
3373
+ this.cdr.markForCheck();
3374
+ }
3375
+ onMouseUp() {
3376
+ if (this.resizing) {
3377
+ this._suppressNextHeaderClick = true;
3378
+ setTimeout(() => { this._suppressNextHeaderClick = false; }, 100);
3379
+ }
3380
+ this.resizing = null;
3381
+ this.cdr.markForCheck();
3382
+ }
3383
+ onColumnDragStarted() { this._columnDragJustEnded = false; }
3384
+ onColumnDragEnded() {
3385
+ this._columnDragJustEnded = true;
3386
+ setTimeout(() => { this._columnDragJustEnded = false; }, 100);
3387
+ }
3388
+ showAllColumns() {
3389
+ this._cols.forEach(c => (c._hidden = false));
3390
+ this.recomputePinnedOffsets();
3391
+ this.updateVisibleCols();
3392
+ this.cdr.markForCheck();
3393
+ }
3394
+ // ─── Header scroll sync ───────────────────────────────────────────────────────
3395
+ onBodyScroll(event) {
3396
+ const scrollLeft = event.target.scrollLeft;
3397
+ if (this.headerScrollRef) {
3398
+ this.headerScrollRef.nativeElement.scrollLeft = scrollLeft;
3399
+ }
3400
+ }
3401
+ // ─── Row selection ────────────────────────────────────────────────────────────
3402
+ onRowClick(row, event) {
3403
+ this.rowClick.emit(row);
3404
+ if (!this.rowSelection)
3405
+ return;
3406
+ if (this.rowSelection === 'single') {
3407
+ this.selectedRows.clear();
3408
+ this.selectedRows.add(row);
3409
+ }
3410
+ else {
3411
+ if (this.selectedRows.has(row)) {
3412
+ this.selectedRows.delete(row);
3413
+ }
3414
+ else {
3415
+ if (!event.ctrlKey)
3416
+ this.selectedRows.clear();
3417
+ this.selectedRows.add(row);
3418
+ }
3419
+ }
3420
+ this.selectionChange.emit([...this.selectedRows]);
3421
+ }
3422
+ toggleRowSelect(row, event) {
3423
+ event.stopPropagation();
3424
+ this.selectedRows.has(row) ? this.selectedRows.delete(row) : this.selectedRows.add(row);
3425
+ this.selectionChange.emit([...this.selectedRows]);
3426
+ }
3427
+ toggleSelectAll(checked) {
3428
+ this.selectedRows.clear();
3429
+ if (checked) {
3430
+ this.flatRows.filter(r => r._type === 'data').forEach((r) => this.selectedRows.add(r._data));
3431
+ }
3432
+ this.selectionChange.emit([...this.selectedRows]);
3433
+ }
3434
+ get allSelected() {
3435
+ const dr = this.flatRows.filter(r => r._type === 'data');
3436
+ return dr.length > 0 && dr.every((r) => this.selectedRows.has(r._data));
3437
+ }
3438
+ get someSelected() {
3439
+ return this.selectedRows.size > 0 && !this.allSelected;
3440
+ }
3441
+ // ─── Pagination ───────────────────────────────────────────────────────────────
3442
+ get _total() { return this.autoMode ? this._filteredTotal : this.totalRecords; }
3443
+ get totalPages() { return Math.ceil(this._total / this.pageSize) || 1; }
3444
+ get pageNumbers() {
3445
+ const total = this.totalPages;
3446
+ if (total <= 7)
3447
+ return Array.from({ length: total }, (_, i) => i + 1);
3448
+ const pages = [1];
3449
+ if (this.currentPage > 3)
3450
+ pages.push(-1);
3451
+ for (let i = Math.max(2, this.currentPage - 1); i <= Math.min(total - 1, this.currentPage + 1); i++)
3452
+ pages.push(i);
3453
+ if (this.currentPage < total - 2)
3454
+ pages.push(-1);
3455
+ pages.push(total);
3456
+ return pages;
3457
+ }
3458
+ get startRecord() { return this._total === 0 ? 0 : (this.currentPage - 1) * this.pageSize + 1; }
3459
+ get endRecord() { return Math.min(this.currentPage * this.pageSize, this._total); }
3460
+ goToPage(page) {
3461
+ if (page < 1 || page > this.totalPages)
3462
+ return;
3463
+ this.currentPage = page;
3464
+ this.processData();
3465
+ this.pageChange.emit({ page: this.currentPage, pageSize: this.pageSize });
3466
+ }
3467
+ onPageSizeChange(size) {
3468
+ this.pageSize = Number(size);
3469
+ this.currentPage = 1;
3470
+ this.processData();
3471
+ this.pageSizeChange.emit({ page: 1, pageSize: this.pageSize });
3472
+ }
3473
+ // ─── Export CSV ───────────────────────────────────────────────────────────────
3474
+ exportCsv() {
3475
+ const cols = this.visibleCols;
3476
+ const header = cols.map(c => `"${c.label}"`).join(',');
3477
+ const filteredRows = this.getFilteredRows();
3478
+ const rows = filteredRows.map(row => cols.map(col => `"${String(this.getCellDisplay(row, col)).replace(/"/g, '""')}"`).join(','));
3479
+ const blob = new Blob([[header, ...rows].join('\n')], { type: 'text/csv;charset=utf-8;' });
3480
+ const a = Object.assign(document.createElement('a'), {
3481
+ href: URL.createObjectURL(blob), download: `${this.title || 'report'}.csv`,
3482
+ });
3483
+ a.click();
3484
+ URL.revokeObjectURL(a.href);
3485
+ }
3486
+ // ─── Aggregates ───────────────────────────────────────────────────────────────
3487
+ getAggregate(col) {
3488
+ if (!col.aggregate)
3489
+ return '';
3490
+ const vals = this.flatRows
3491
+ .filter(r => r._type === 'data')
3492
+ .map((r) => Number(r._data[col.key]))
3493
+ .filter(v => !isNaN(v));
3494
+ if (!vals.length)
3495
+ return '--';
3496
+ const fmt = (n) => this.decimalPipe.transform(n, '1.0-2') ?? String(n);
3497
+ switch (col.aggregate) {
3498
+ case 'sum': return fmt(vals.reduce((a, b) => a + b, 0));
3499
+ case 'avg': return fmt(vals.reduce((a, b) => a + b, 0) / vals.length);
3500
+ case 'count': return String(vals.length);
3501
+ case 'min': return fmt(Math.min(...vals));
3502
+ case 'max': return fmt(Math.max(...vals));
3503
+ default: return '';
3504
+ }
3505
+ }
3506
+ hasAnyAggregate() { return this.visibleCols.some(c => !!c.aggregate); }
3507
+ // ─── Cell display ─────────────────────────────────────────────────────────────
3508
+ getCellDisplay(row, col) {
3509
+ return this.getCellDisplayByKey(row[col.key], col, row);
3510
+ }
3511
+ getCellDisplayByKey(raw, col, row) {
3512
+ if (col.displayFn && row !== undefined)
3513
+ return col.displayFn(raw, row);
3514
+ if (col.enums?.length)
3515
+ return col.enums.find(e => e.value === raw)?.label ?? (raw ?? '--');
3516
+ if (raw === null || raw === undefined || raw === '')
3517
+ return '--';
3518
+ switch (col.displayType) {
3519
+ case 'date': return this.datePipe.transform(raw, 'MM/dd/yyyy') ?? '--';
3520
+ case 'datetime': return this.datePipe.transform(raw, 'MM/dd/yyyy HH:mm') ?? '--';
3521
+ case 'time': return this.datePipe.transform(raw, 'HH:mm') ?? '--';
3522
+ case 'currency': return '$' + Number(raw).toLocaleString('en-US', { minimumFractionDigits: 2 });
3523
+ case 'number': return Number(raw).toLocaleString();
3524
+ case 'percentage': return Number(raw).toFixed(2) + '%';
3525
+ default: return String(raw);
3526
+ }
3527
+ }
3528
+ getCellClass(row, col) {
3529
+ if (!col.cellClass)
3530
+ return '';
3531
+ return typeof col.cellClass === 'function' ? col.cellClass(row[col.key], row) : col.cellClass;
3532
+ }
3533
+ // ─── Copy cell ────────────────────────────────────────────────────────────────
3534
+ copyCell(value, event) {
3535
+ event.stopPropagation();
3536
+ navigator.clipboard?.writeText(value).then(() => {
3537
+ this.copiedCell = value;
3538
+ setTimeout(() => (this.copiedCell = null), 1500);
3539
+ });
3540
+ }
3541
+ // ─── Skeleton / empty ─────────────────────────────────────────────────────────
3542
+ get skeletonRows() { return Array.from({ length: Math.min(this.pageSize, 15) }); }
3543
+ get isEmpty() {
3544
+ return !this.loading && this.flatRows.length === 0;
3545
+ }
3546
+ // ─── Global click close ───────────────────────────────────────────────────────
3547
+ onDocumentClick() {
3548
+ this.openFilterKey = null;
3549
+ this.columnMenuKey = null;
3550
+ }
3551
+ // ─── TrackBy ─────────────────────────────────────────────────────────────────
3552
+ trackByRow = (_, row) => row._type === 'data' ? `d-${row._rowIndex}` : `g-${row._path}`;
3553
+ trackByCol = (_, col) => col.key;
3554
+ trackByStr = (_, s) => s;
3555
+ asGroupRow = (row) => row;
3556
+ asDataRow = (row) => row;
3557
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: OslReportGrid, deps: [], target: i0.ɵɵFactoryTarget.Component });
3558
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: OslReportGrid, isStandalone: false, selector: "osl-report-grid", inputs: { columns: "columns", datasource: "datasource", loading: "loading", totalRecords: "totalRecords", autoMode: "autoMode", isPaginated: "isPaginated", pageSize: "pageSize", tableHeight: "tableHeight", striped: "striped", exportable: "exportable", rowHeight: "rowHeight", rowSelection: "rowSelection", showAggregates: "showAggregates", title: "title" }, outputs: { pageChange: "pageChange", pageSizeChange: "pageSizeChange", sortChange: "sortChange", rowClick: "rowClick", selectionChange: "selectionChange" }, host: { listeners: { "document:mousemove": "onMouseMove($event)", "document:mouseup": "onMouseUp()", "document:click": "onDocumentClick()" } }, providers: [DatePipe, DecimalPipe], viewQueries: [{ propertyName: "headerScrollRef", first: true, predicate: ["headerScrollRef"], descendants: true }, { propertyName: "bodyScrollRef", first: true, predicate: ["bodyScrollRef"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"osl-rg\" (click)=\"$event.stopPropagation()\">\n\n <!-- \u2550\u2550 TOOLBAR \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n <div class=\"osl-rg-toolbar\">\n <div class=\"osl-rg-toolbar__left\">\n @if (title) { <span class=\"osl-rg-title\">{{ title }}</span> }\n @if (!loading) {\n @if (_filteredTotal !== datasource.length) {\n <span class=\"osl-rg-filter-badge\">\n <span class=\"osl-rg-filter-badge__dot\"></span>\n {{ _filteredTotal | number }} of {{ datasource.length | number }} rows\n </span>\n } @else {\n <span class=\"osl-rg-record-count\">{{ datasource.length | number }} rows</span>\n }\n }\n </div>\n <div class=\"osl-rg-toolbar__right\">\n\n <!-- Global search -->\n <div class=\"osl-rg-global-search\">\n <svg class=\"osl-rg-global-search__icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <circle cx=\"11\" cy=\"11\" r=\"8\"/><path d=\"m21 21-4.35-4.35\"/>\n </svg>\n <input class=\"osl-rg-global-search__input\" placeholder=\"Search all columns\u2026\"\n [(ngModel)]=\"globalSearch\" (ngModelChange)=\"currentPage=1; processData()\" />\n @if (globalSearch) {\n <button class=\"osl-rg-global-search__clear\" (click)=\"globalSearch=''; processData()\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\"><path d=\"M18 6 6 18M6 6l12 12\"/></svg>\n </button>\n }\n </div>\n\n <!-- Sort active badge -->\n @if (sortStates.size > 0) {\n <button class=\"osl-rg-toolbar-btn osl-rg-toolbar-btn--active\" (click)=\"clearSort()\" title=\"Clear sort\">\n <svg class=\"rg-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M11 5H4M11 9H4M11 13H4M18 4v16M15 7l3-3 3 3M15 17l3 3 3-3\"/>\n </svg>\n <span>{{ sortStates.size }} sort{{ sortStates.size > 1 ? 's' : '' }}</span>\n <svg class=\"rg-icon rg-icon--xs\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\"><path d=\"M18 6 6 18M6 6l12 12\"/></svg>\n </button>\n }\n\n <!-- Filter active badge -->\n @if (hasAnyFilter) {\n <button class=\"osl-rg-toolbar-btn osl-rg-toolbar-btn--danger\" (click)=\"clearAllFilters()\" title=\"Clear all filters\">\n <svg class=\"rg-icon\" viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M22 3H2l8 9.46V19l4 2V12.46z\"/></svg>\n <span>Filtered</span>\n <svg class=\"rg-icon rg-icon--xs\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\"><path d=\"M18 6 6 18M6 6l12 12\"/></svg>\n </button>\n }\n\n <!-- Group toggle -->\n <button class=\"osl-rg-toolbar-btn\" [class.osl-rg-toolbar-btn--on]=\"showGroupPanel\"\n (click)=\"showGroupPanel=!showGroupPanel\" title=\"Group panel\">\n <svg class=\"rg-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <rect x=\"2\" y=\"7\" width=\"20\" height=\"14\" rx=\"2\"/><path d=\"M16 3H8a2 2 0 0 0-2 2v2h12V5a2 2 0 0 0-2-2z\"/>\n </svg>\n <span>Group</span>\n </button>\n\n <!-- Expand / collapse when grouped -->\n @if (activeGroups.length > 0) {\n <button class=\"osl-rg-toolbar-btn\" (click)=\"expandAll()\" title=\"Expand all\">\n <svg class=\"rg-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"m7 20 5-5 5 5M7 4l5 5 5-5\"/></svg>\n </button>\n <button class=\"osl-rg-toolbar-btn\" (click)=\"collapseAll()\" title=\"Collapse all\">\n <svg class=\"rg-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"m7 15 5-5 5 5M7 9l5 5 5 5\"/></svg>\n </button>\n }\n\n <!-- Auto-size all -->\n <button class=\"osl-rg-toolbar-btn\" (click)=\"autoSizeAll()\" title=\"Auto-size all columns\">\n <svg class=\"rg-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M21 6H3M21 18H3\"/><path d=\"M8 3 3 6l5 3M16 3l5 3-5 3M8 15l-5 3 5 3M16 15l5 3-5 3\"/>\n </svg>\n <span>Auto-size</span>\n </button>\n\n <!-- Column config -->\n <button class=\"osl-rg-toolbar-btn\" [class.osl-rg-toolbar-btn--on]=\"showColumnConfig\"\n (click)=\"showColumnConfig=!showColumnConfig; $event.stopPropagation()\" title=\"Column settings\">\n <svg class=\"rg-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M4 6h16M4 12h16M4 18h16\"/>\n <circle cx=\"8\" cy=\"6\" r=\"2\" fill=\"white\" stroke=\"currentColor\" stroke-width=\"2\"/>\n <circle cx=\"16\" cy=\"12\" r=\"2\" fill=\"white\" stroke=\"currentColor\" stroke-width=\"2\"/>\n <circle cx=\"8\" cy=\"18\" r=\"2\" fill=\"white\" stroke=\"currentColor\" stroke-width=\"2\"/>\n </svg>\n <span>Columns</span>\n </button>\n\n <!-- Export Excel -->\n @if (exportable) {\n <button class=\"osl-rg-toolbar-btn osl-rg-toolbar-btn--excel\" (click)=\"exportCsv()\" title=\"Export to Excel (CSV)\">\n <svg class=\"rg-icon rg-icon--excel\" viewBox=\"0 0 24 24\">\n <rect width=\"24\" height=\"24\" rx=\"3\" fill=\"#1D6F42\"/>\n <path d=\"M13.5 3H7a1 1 0 0 0-1 1v16a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V7.5L13.5 3z\" fill=\"#fff\" opacity=\".15\"/>\n <path d=\"M13.5 3v4.5H18L13.5 3z\" fill=\"#fff\" opacity=\".3\"/>\n <path d=\"M8 9.5l2.3 3.5L8 16.5h1.6l1.4-2.3 1.4 2.3H14l-2.3-3.5 2.3-3.5h-1.6l-1.4 2.3-1.4-2.3H8z\" fill=\"#fff\"/>\n </svg>\n <span>Export</span>\n </button>\n }\n </div>\n </div>\n\n <!-- \u2550\u2550 GROUP PANEL \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n @if (showGroupPanel) {\n <div class=\"osl-rg-group-panel\">\n <span class=\"osl-rg-group-panel__label\">Group by:</span>\n @if (activeGroups.length === 0) {\n <span class=\"osl-rg-group-panel__hint\">Use the \u22EE column menu \u2192 \"Group by this\"</span>\n }\n <div class=\"osl-rg-group-chips\" cdkDropList cdkDropListOrientation=\"horizontal\"\n [cdkDropListData]=\"activeGroups\" (cdkDropListDropped)=\"onGroupPanelDrop($event)\">\n @for (key of activeGroups; track key) {\n <div class=\"osl-rg-group-chip\" cdkDrag>\n <svg cdkDragHandle class=\"rg-drag-handle\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <circle cx=\"9\" cy=\"5\" r=\"1.5\"/><circle cx=\"15\" cy=\"5\" r=\"1.5\"/>\n <circle cx=\"9\" cy=\"12\" r=\"1.5\"/><circle cx=\"15\" cy=\"12\" r=\"1.5\"/>\n <circle cx=\"9\" cy=\"19\" r=\"1.5\"/><circle cx=\"15\" cy=\"19\" r=\"1.5\"/>\n </svg>\n <span>{{ getGroupColLabel(key) }}</span>\n <button class=\"osl-rg-group-chip__remove\" (click)=\"removeGroup(key)\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\"><path d=\"M18 6 6 18M6 6l12 12\"/></svg>\n </button>\n </div>\n }\n </div>\n @if (activeGroups.length > 0) {\n <button class=\"osl-rg-group-panel__clear\" (click)=\"clearGroups()\">Clear groups</button>\n }\n </div>\n }\n\n <!-- \u2550\u2550 STICKY HEADER \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n <div class=\"osl-rg-header-wrap\" #headerScrollRef>\n <div class=\"osl-rg-header-row\" [style.minWidth.px]=\"totalWidth\">\n\n <!-- Select-all checkbox -->\n @if (rowSelection === 'multiple') {\n <div class=\"osl-rg-th osl-rg-th--checkbox\">\n <input type=\"checkbox\" class=\"osl-rg-checkbox\"\n [checked]=\"allSelected\" [indeterminate]=\"someSelected\"\n (change)=\"toggleSelectAll($any($event.target).checked)\" />\n </div>\n }\n\n <!-- Column headers (drag-to-reorder) -->\n <div class=\"osl-rg-header-cols\" cdkDropList cdkDropListOrientation=\"horizontal\"\n [cdkDropListData]=\"_cols\" (cdkDropListDropped)=\"onColumnReorder($event)\"\n style=\"display:flex;flex:1;min-width:0;\">\n\n @for (col of visibleCols; track col.key) {\n <div class=\"osl-rg-th\"\n [class.osl-rg-th--pinned]=\"col._pinned\"\n [class.osl-rg-th--sorted]=\"getSortState(col.key)\"\n [class.osl-rg-th--filtered]=\"hasActiveFilter(col.key)\"\n [style.width.px]=\"col._width\"\n [style.minWidth.px]=\"col._width\"\n [style.left.px]=\"col._pinned ? col._stickyLeft : null\"\n [style.position]=\"col._pinned ? 'sticky' : 'relative'\"\n [style.zIndex]=\"col._pinned ? 3 : 1\"\n cdkDrag [cdkDragDisabled]=\"col._pinned\"\n (cdkDragStarted)=\"onColumnDragStarted()\"\n (cdkDragEnded)=\"onColumnDragEnded()\"\n (click)=\"onHeaderClick(col, $event)\">\n\n <div *cdkDragPlaceholder class=\"osl-rg-th-drag-placeholder\"></div>\n\n <!-- Pin badge -->\n @if (col._pinned) {\n <span class=\"osl-rg-th-pin-badge\" title=\"Pinned\">\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" width=\"9\" height=\"9\"><path d=\"M16 12V4h1V2H7v2h1v8l-2 2v2h5.2v6h1.6v-6H18v-2l-2-2z\"/></svg>\n </span>\n }\n\n <!-- Label row -->\n <div class=\"osl-rg-th-content\">\n <span class=\"osl-rg-th-label\" [title]=\"col.label\">{{ col.label }}</span>\n <div class=\"osl-rg-th-actions\">\n\n <!-- Sort indicator -->\n @if (col.sortable) {\n <span class=\"osl-rg-sort-icon\"\n [class.osl-rg-sort-icon--asc]=\"getSortState(col.key)?.asc === true\"\n [class.osl-rg-sort-icon--desc]=\"getSortState(col.key)?.asc === false\">\n @if (getSortState(col.key); as s) {\n @if (s.asc) {\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M12 19V5m-7 7 7-7 7 7\"/></svg>\n } @else {\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M12 5v14m7-7-7 7-7-7\"/></svg>\n }\n @if (sortStates.size > 1) { <sup class=\"osl-rg-sort-badge\">{{ s.index + 1 }}</sup> }\n } @else {\n <svg class=\"osl-rg-sort-idle\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M8 9l4-4 4 4M16 15l-4 4-4-4\"/></svg>\n }\n </span>\n }\n\n <!-- Excel filter button -->\n @if (col.filterable) {\n <button class=\"osl-rg-filter-btn\" [class.osl-rg-filter-btn--active]=\"hasActiveFilter(col.key)\"\n (click)=\"openExcelFilter(col.key, $event)\" title=\"Filter\">\n @if (hasActiveFilter(col.key)) {\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M22 3H2l8 9.46V19l4 2V12.46z\"/></svg>\n } @else {\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polygon points=\"22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3\"/></svg>\n }\n </button>\n }\n\n <!-- Column menu -->\n <button class=\"osl-rg-col-menu-btn\" (click)=\"openColumnMenu(col.key, $event)\" title=\"Column options\">\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><circle cx=\"12\" cy=\"5\" r=\"2\"/><circle cx=\"12\" cy=\"12\" r=\"2\"/><circle cx=\"12\" cy=\"19\" r=\"2\"/></svg>\n </button>\n\n </div>\n </div>\n\n <!-- Per-column search -->\n @if (col.searchable) {\n <div class=\"osl-rg-col-search\" (click)=\"$event.stopPropagation()\">\n <svg class=\"osl-rg-col-search__icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"m18 18-3.5-3.5\"/>\n </svg>\n <input class=\"osl-rg-col-search__input\" [placeholder]=\"col.label + '\u2026'\"\n [ngModel]=\"columnSearch[col.key]\" (ngModelChange)=\"onColumnSearch(col.key, $event)\" />\n @if (columnSearch[col.key]) {\n <button class=\"osl-rg-col-search__clear\" (click)=\"columnSearch[col.key]=''; processData()\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\"><path d=\"M18 6 6 18M6 6l12 12\"/></svg>\n </button>\n }\n </div>\n }\n\n <!-- Resize handle -->\n @if (col.resizable) {\n <div class=\"osl-rg-resize-handle\" (mousedown)=\"startResize(col, $event)\" (dblclick)=\"autoSizeColumn(col, $event)\" (click)=\"$event.stopPropagation()\"></div>\n }\n\n\n </div><!-- /osl-rg-th -->\n }\n\n </div><!-- /header-cols -->\n </div><!-- /header-row -->\n </div><!-- /header-wrap -->\n\n <!-- \u2550\u2550 SCROLLABLE BODY \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n <div class=\"osl-rg-body\" [style.height]=\"tableHeight\" #bodyScrollRef (scroll)=\"onBodyScroll($event)\">\n <div class=\"osl-rg-inner\" [style.minWidth.px]=\"totalWidth\">\n\n <!-- Skeleton loading -->\n @if (loading) {\n @for (sk of skeletonRows; track $index) {\n <div class=\"osl-rg-row osl-rg-row--skeleton\" [style.height.px]=\"rowHeight\">\n @if (rowSelection === 'multiple') {\n <div class=\"osl-rg-td osl-rg-td--checkbox\"><div class=\"osl-rg-skel\" style=\"width:14px;height:14px;border-radius:3px;\"></div></div>\n }\n @for (col of visibleCols; track col.key) {\n <div class=\"osl-rg-td\" [style.width.px]=\"col._width\" [style.minWidth.px]=\"col._width\">\n <div class=\"osl-rg-skel\" [style.width]=\"$index % 3 === 0 ? '55%' : $index % 3 === 1 ? '80%' : '65%'\"></div>\n </div>\n }\n </div>\n }\n\n } @else if (isEmpty) {\n <!-- Empty state -->\n <div class=\"osl-rg-empty\">\n <div class=\"osl-rg-empty__icon\">\n <svg viewBox=\"0 0 48 48\" fill=\"none\" stroke=\"#d1d5db\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <rect x=\"6\" y=\"6\" width=\"36\" height=\"36\" rx=\"4\"/>\n <path d=\"M6 18h36M6 30h36M18 6v36\"/>\n </svg>\n </div>\n <p class=\"osl-rg-empty__title\">No records found</p>\n <p class=\"osl-rg-empty__sub\">\n @if (hasAnyFilter) { Try adjusting your filters or search terms }\n @else { No data to display }\n </p>\n @if (hasAnyFilter) {\n <button class=\"osl-rg-empty__action\" (click)=\"clearAllFilters()\">Clear all filters</button>\n }\n </div>\n\n } @else {\n <!-- Data rows -->\n @for (row of flatRows; track trackByRow($index, row)) {\n\n @if (row._type === 'group') {\n <div class=\"osl-rg-group-row\"\n [class.osl-rg-group-row--l0]=\"asGroupRow(row)._level === 0\"\n [class.osl-rg-group-row--l1]=\"asGroupRow(row)._level === 1\"\n [class.osl-rg-group-row--l2]=\"asGroupRow(row)._level >= 2\"\n [style.height.px]=\"rowHeight\"\n [style.paddingLeft.px]=\"asGroupRow(row)._level * 20 + 12\"\n (click)=\"toggleGroup(asGroupRow(row)._path)\">\n @if (asGroupRow(row)._expanded) {\n <svg class=\"osl-rg-group-chevron\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"m6 9 6 6 6-6\"/></svg>\n } @else {\n <svg class=\"osl-rg-group-chevron\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"m9 18 6-6-6-6\"/></svg>\n }\n <span class=\"osl-rg-group-row__key\">{{ asGroupRow(row)._colLabel }}:</span>\n <span class=\"osl-rg-group-row__value\">{{ asGroupRow(row)._label }}</span>\n <span class=\"osl-rg-group-row__count\">{{ asGroupRow(row)._count | number }} rows</span>\n </div>\n\n } @else {\n <div class=\"osl-rg-row\"\n [class.osl-rg-row--striped]=\"striped && asDataRow(row)._rowIndex % 2 === 1\"\n [class.osl-rg-row--selected]=\"selectedRows.has(asDataRow(row)._data)\"\n [style.height.px]=\"rowHeight\"\n (click)=\"onRowClick(asDataRow(row)._data, $event)\">\n\n @if (rowSelection === 'multiple') {\n <div class=\"osl-rg-td osl-rg-td--checkbox\">\n <input type=\"checkbox\" class=\"osl-rg-checkbox\"\n [checked]=\"selectedRows.has(asDataRow(row)._data)\"\n (change)=\"toggleRowSelect(asDataRow(row)._data, $event)\" />\n </div>\n }\n\n @for (col of visibleCols; track col.key) {\n <div class=\"osl-rg-td\"\n [class.osl-rg-td--pinned]=\"col._pinned\"\n [ngClass]=\"getCellClass(asDataRow(row)._data, col)\"\n [style.width.px]=\"col._width\"\n [style.minWidth.px]=\"col._width\"\n [style.left.px]=\"col._pinned ? col._stickyLeft : null\"\n [style.position]=\"col._pinned ? 'sticky' : 'relative'\"\n [style.zIndex]=\"col._pinned ? 2 : 0\"\n [style.textAlign]=\"col.align ?? 'left'\"\n (dblclick)=\"copyCell(getCellDisplay(asDataRow(row)._data, col), $event)\"\n [title]=\"getCellDisplay(asDataRow(row)._data, col)\">\n <span class=\"osl-rg-cell-text\">{{ getCellDisplay(asDataRow(row)._data, col) }}</span>\n </div>\n }\n\n </div>\n }\n\n }\n }\n\n </div><!-- /osl-rg-inner -->\n </div><!-- /osl-rg-body -->\n\n <!-- \u2550\u2550 AGGREGATE FOOTER \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n @if (showAggregates && hasAnyAggregate() && !loading) {\n <div class=\"osl-rg-aggregate-row\" [style.minWidth.px]=\"totalWidth\">\n @if (rowSelection === 'multiple') { <div class=\"osl-rg-agg-cell osl-rg-agg-cell--checkbox\"></div> }\n @for (col of visibleCols; track col.key) {\n <div class=\"osl-rg-agg-cell\" [style.width.px]=\"col._width\" [style.minWidth.px]=\"col._width\" [style.textAlign]=\"col.align ?? 'left'\">\n @if (col.aggregate) {\n <span class=\"osl-rg-agg-label\">{{ col.aggregate | uppercase }}</span>\n <span class=\"osl-rg-agg-value\">{{ getAggregate(col) }}</span>\n }\n </div>\n }\n </div>\n }\n\n <!-- \u2550\u2550 PAGINATION \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n @if (isPaginated) {\n <div class=\"osl-rg-pagination\">\n <span class=\"osl-rg-pagination__info\">\n @if (loading) { Loading\u2026 }\n @else if (_total > 0) { Showing <strong>{{ startRecord | number }}\u2013{{ endRecord | number }}</strong> of <strong>{{ _total | number }}</strong> records }\n @else { No records }\n </span>\n <div class=\"osl-rg-pagination__controls\">\n <button class=\"osl-rg-page-btn osl-rg-page-btn--nav\" (click)=\"goToPage(1)\" [disabled]=\"currentPage===1||loading\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" width=\"13\" height=\"13\"><path d=\"m11 17-5-5 5-5M18 17l-5-5 5-5\"/></svg>\n </button>\n <button class=\"osl-rg-page-btn osl-rg-page-btn--nav\" (click)=\"goToPage(currentPage-1)\" [disabled]=\"currentPage===1||loading\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" width=\"13\" height=\"13\"><path d=\"m15 18-6-6 6-6\"/></svg>\n Prev\n </button>\n @for (page of pageNumbers; track $index) {\n @if (page === -1) { <span class=\"osl-rg-page-ellipsis\">\u2026</span> }\n @else {\n <button class=\"osl-rg-page-btn osl-rg-page-btn--num\" [class.osl-rg-page-btn--active]=\"page===currentPage\"\n [disabled]=\"loading\" (click)=\"goToPage(page)\">{{ page }}</button>\n }\n }\n <button class=\"osl-rg-page-btn osl-rg-page-btn--nav\" (click)=\"goToPage(currentPage+1)\" [disabled]=\"currentPage===totalPages||loading\">\n Next\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" width=\"13\" height=\"13\"><path d=\"m9 18 6-6-6-6\"/></svg>\n </button>\n <button class=\"osl-rg-page-btn osl-rg-page-btn--nav\" (click)=\"goToPage(totalPages)\" [disabled]=\"currentPage===totalPages||loading\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" width=\"13\" height=\"13\"><path d=\"m13 17 5-5-5-5M6 17l5-5-5-5\"/></svg>\n </button>\n </div>\n <div class=\"osl-rg-pagination__size\">\n <span class=\"osl-rg-pagination__size-label\">Rows per page</span>\n <select class=\"osl-rg-page-size\" [ngModel]=\"pageSize\" (ngModelChange)=\"onPageSizeChange($event)\" [disabled]=\"loading\">\n @for (opt of pageSizeOptions; track opt) { <option [value]=\"opt\">{{ opt }}</option> }\n </select>\n </div>\n </div>\n }\n\n</div><!-- /osl-rg -->\n\n\n<!-- \u2550\u2550 EXCEL FILTER DROPDOWN (fixed, outside overflow containers) \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n@if (openFilterKey && excelFilterState[openFilterKey]) {\n <div class=\"osl-rg-excel-filter\"\n [style.top.px]=\"filterDropdownPos.top\"\n [style.left.px]=\"filterDropdownPos.left\"\n (click)=\"$event.stopPropagation()\">\n <div class=\"osl-rg-excel-filter__header\">\n <div class=\"osl-rg-excel-filter__title\">\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" width=\"12\" height=\"12\" style=\"flex-shrink:0;\"><path d=\"M22 3H2l8 9.46V19l4 2V12.46z\"/></svg>\n Filter: {{ getColumnByKey(openFilterKey)?.label }}\n </div>\n @if (hasActiveFilter(openFilterKey)) {\n <button class=\"osl-rg-excel-filter__clear-btn\" (click)=\"clearExcelFilter(openFilterKey!)\">Clear</button>\n }\n </div>\n <div class=\"osl-rg-excel-filter__search\">\n <input class=\"osl-rg-excel-filter__search-input\" placeholder=\"Search values\u2026\"\n [(ngModel)]=\"excelFilterState[openFilterKey].search\" />\n </div>\n <div class=\"osl-rg-excel-filter__select-all\">\n <label class=\"osl-rg-excel-filter__item osl-rg-excel-filter__item--all\">\n <input type=\"checkbox\" class=\"osl-rg-checkbox\" [checked]=\"isAllExcelChecked(openFilterKey)\"\n (change)=\"toggleAllExcelFilter(openFilterKey!, $any($event.target).checked)\" />\n <span class=\"osl-rg-excel-filter__item-label\">(Select All)</span>\n </label>\n </div>\n <div class=\"osl-rg-excel-filter__list\">\n @for (item of getFilteredExcelItems(openFilterKey); track item.value) {\n <label class=\"osl-rg-excel-filter__item\">\n <input type=\"checkbox\" class=\"osl-rg-checkbox\" [(ngModel)]=\"item.checked\" />\n <span class=\"osl-rg-excel-filter__item-label\">{{ item.label }}</span>\n </label>\n }\n </div>\n <div class=\"osl-rg-excel-filter__footer\">\n <button class=\"osl-rg-excel-filter__apply\" (click)=\"applyExcelFilter(openFilterKey!)\">Apply Filter</button>\n <button class=\"osl-rg-excel-filter__cancel\" (click)=\"openFilterKey=null\">Cancel</button>\n </div>\n </div>\n}\n\n\n<!-- \u2550\u2550 COLUMN CONTEXT MENU (fixed) \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n@if (columnMenuKey && getColumnByKey(columnMenuKey); as menuCol) {\n <div class=\"osl-rg-col-menu\"\n [style.top.px]=\"columnMenuPos.top\" [style.left.px]=\"columnMenuPos.left\"\n (click)=\"$event.stopPropagation()\">\n <div class=\"osl-rg-col-menu__header\">{{ menuCol.label }}</div>\n @if (menuCol.sortable) {\n <button class=\"osl-rg-col-menu__item\"\n (click)=\"sortStates.clear(); sortStates.set(menuCol.key,{key:menuCol.key,asc:true,index:0}); sortCounter=1; processData(); closeColumnMenu()\">\n <svg class=\"rg-icon rg-icon--sm\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M12 19V5m-7 7 7-7 7 7\"/></svg>\n Sort A \u2192 Z\n </button>\n <button class=\"osl-rg-col-menu__item\"\n (click)=\"sortStates.clear(); sortStates.set(menuCol.key,{key:menuCol.key,asc:false,index:0}); sortCounter=1; processData(); closeColumnMenu()\">\n <svg class=\"rg-icon rg-icon--sm\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M12 5v14m7-7-7 7-7-7\"/></svg>\n Sort Z \u2192 A\n </button>\n }\n @if (menuCol.groupable) {\n <div class=\"osl-rg-col-menu__divider\"></div>\n <button class=\"osl-rg-col-menu__item\" (click)=\"addGroup(menuCol.key); closeColumnMenu()\">\n <svg class=\"rg-icon rg-icon--sm\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"2\" y=\"7\" width=\"20\" height=\"14\" rx=\"2\"/><path d=\"M16 3H8a2 2 0 0 0-2 2v2h12V5a2 2 0 0 0-2-2z\"/></svg>\n Group by this\n </button>\n }\n <div class=\"osl-rg-col-menu__divider\"></div>\n <button class=\"osl-rg-col-menu__item\" (click)=\"toggleColumnPin(menuCol)\">\n <svg class=\"rg-icon rg-icon--sm\" viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M16 12V4h1V2H7v2h1v8l-2 2v2h5.2v6h1.6v-6H18v-2l-2-2z\"/></svg>\n {{ menuCol._pinned ? 'Unpin column' : 'Pin to left' }}\n </button>\n <button class=\"osl-rg-col-menu__item\" (click)=\"autoSizeColumn(menuCol); closeColumnMenu()\">\n <svg class=\"rg-icon rg-icon--sm\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M21 6H3M21 18H3\"/><path d=\"M8 3 3 6l5 3M16 3l5 3-5 3M8 15l-5 3 5 3M16 15l5 3-5 3\"/></svg>\n Auto-size column\n </button>\n <div class=\"osl-rg-col-menu__divider\"></div>\n <button class=\"osl-rg-col-menu__item osl-rg-col-menu__item--danger\" (click)=\"toggleColumnVisibility(menuCol); closeColumnMenu()\">\n <svg class=\"rg-icon rg-icon--sm\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24M1 1l22 22\"/>\n </svg>\n Hide column\n </button>\n </div>\n}\n\n\n<!-- \u2550\u2550 COLUMN CONFIG SIDE DRAWER \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n@if (showColumnConfig) {\n <div class=\"osl-rg-config-backdrop\" (click)=\"showColumnConfig=false\"></div>\n <div class=\"osl-rg-config-panel\" (click)=\"$event.stopPropagation()\">\n <div class=\"osl-rg-config-panel__header\">\n <div class=\"osl-rg-config-panel__title\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" width=\"16\" height=\"16\">\n <path d=\"M4 6h16M4 12h16M4 18h16\"/>\n <circle cx=\"8\" cy=\"6\" r=\"2\" fill=\"white\" stroke=\"currentColor\" stroke-width=\"2\"/>\n <circle cx=\"16\" cy=\"12\" r=\"2\" fill=\"white\" stroke=\"currentColor\" stroke-width=\"2\"/>\n <circle cx=\"8\" cy=\"18\" r=\"2\" fill=\"white\" stroke=\"currentColor\" stroke-width=\"2\"/>\n </svg>\n Column Settings\n </div>\n <button class=\"osl-rg-config-panel__close\" (click)=\"showColumnConfig=false\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\"><path d=\"M18 6 6 18M6 6l12 12\"/></svg>\n </button>\n </div>\n <div class=\"osl-rg-config-panel__actions\">\n <button class=\"osl-rg-config-action\" (click)=\"autoSizeAll()\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" width=\"13\" height=\"13\"><path d=\"M21 6H3M21 18H3\"/><path d=\"M8 3 3 6l5 3M16 3l5 3-5 3\"/></svg>\n Auto-size all\n </button>\n <button class=\"osl-rg-config-action\" (click)=\"showAllColumns()\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" width=\"13\" height=\"13\"><path d=\"M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z\"/><circle cx=\"12\" cy=\"12\" r=\"3\"/></svg>\n Show all\n </button>\n </div>\n <div class=\"osl-rg-config-hint\">Drag to reorder \u00B7 Toggle to show/hide \u00B7 Pin to freeze</div>\n <div class=\"osl-rg-config-list\" cdkDropList (cdkDropListDropped)=\"onConfigColumnReorder($event)\">\n @for (col of _cols; track col.key) {\n <div class=\"osl-rg-config-item\" cdkDrag [class.osl-rg-config-item--hidden]=\"col._hidden\">\n <svg cdkDragHandle class=\"rg-drag-handle\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <circle cx=\"9\" cy=\"5\" r=\"1.5\"/><circle cx=\"15\" cy=\"5\" r=\"1.5\"/>\n <circle cx=\"9\" cy=\"12\" r=\"1.5\"/><circle cx=\"15\" cy=\"12\" r=\"1.5\"/>\n <circle cx=\"9\" cy=\"19\" r=\"1.5\"/><circle cx=\"15\" cy=\"19\" r=\"1.5\"/>\n </svg>\n <input type=\"checkbox\" class=\"osl-rg-checkbox\" [checked]=\"!col._hidden\" (change)=\"toggleColumnVisibility(col)\" />\n <span class=\"osl-rg-config-item__label\" [class.osl-rg-config-item__label--hidden]=\"col._hidden\">{{ col.label }}</span>\n <button class=\"osl-rg-config-item__pin-btn\" (click)=\"toggleColumnPin(col)\"\n [class.osl-rg-config-item__pin-btn--active]=\"col._pinned\"\n [title]=\"col._pinned ? 'Unpin' : 'Pin to left'\">\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" width=\"12\" height=\"12\"><path d=\"M16 12V4h1V2H7v2h1v8l-2 2v2h5.2v6h1.6v-6H18v-2l-2-2z\"/></svg>\n </button>\n </div>\n }\n </div>\n </div>\n}\n\n\n<!-- \u2550\u2550 COPY TOAST \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n@if (copiedCell !== null) {\n <div class=\"osl-rg-copy-toast\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" width=\"13\" height=\"13\"><polyline points=\"20 6 9 17 4 12\"/></svg>\n Copied to clipboard\n </div>\n}\n", styles: ["@keyframes rg-pulse{0%,to{opacity:1}50%{opacity:.4}}@keyframes rg-fade-up{0%{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}@keyframes rg-slide-in{0%{opacity:0;transform:translate(20px)}to{opacity:1;transform:translate(0)}}.rg-icon{width:15px;height:15px;flex-shrink:0;display:block}.rg-icon--sm{width:14px;height:14px}.rg-icon--xs{width:12px;height:12px}.rg-icon--excel{width:18px;height:18px}.rg-drag-handle{width:14px;height:14px;flex-shrink:0;color:#d1d5db;cursor:grab;display:block}.rg-drag-handle:active{cursor:grabbing}.osl-rg{position:relative;display:flex;flex-direction:column;width:100%;font-family:inherit;font-size:13px;color:#111827;background:#fff;border:1px solid var(--osl-border-color, #e5e7eb);border-radius:10px;overflow:hidden;box-shadow:0 2px 8px #00000012,0 1px 2px #0000000a;-webkit-user-select:none;user-select:none}.osl-rg-toolbar{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:10px 14px;background:linear-gradient(135deg,#f8faff,#f3f4f6);border-bottom:1px solid var(--osl-border-color, #e5e7eb);flex-shrink:0;flex-wrap:wrap}.osl-rg-toolbar__left{display:flex;align-items:center;gap:10px;flex-wrap:wrap}.osl-rg-toolbar__right{display:flex;align-items:center;gap:6px;flex-wrap:wrap}.osl-rg-title{font-size:14px;font-weight:700;color:#111827;letter-spacing:-.01em}.osl-rg-record-count{display:inline-flex;align-items:center;gap:5px;font-size:12px;color:#9ca3af;font-weight:500}.osl-rg-record-count svg{color:#d1d5db}.osl-rg-filter-badge{display:inline-flex;align-items:center;gap:5px;padding:2px 9px 2px 6px;background:#eff6ff;border:1px solid #bfdbfe;border-radius:12px;font-size:11px;color:#1d4ed8;font-weight:600}.osl-rg-filter-badge__dot{width:6px;height:6px;border-radius:50%;background:#2563eb;animation:rg-pulse 1.5s ease-in-out infinite;flex-shrink:0}.osl-rg-toolbar-btn{display:inline-flex;align-items:center;gap:5px;height:32px;padding:0 11px;border:1px solid var(--osl-border-color, #e5e7eb);border-radius:7px;background:#fff;color:#5a6478;font-size:12px;font-family:inherit;font-weight:500;cursor:pointer;white-space:nowrap;transition:all .15s ease}.osl-rg-toolbar-btn svg{flex-shrink:0}.osl-rg-toolbar-btn:hover{background:#f3f4f6;color:#1f2937;border-color:#c4c9d4;box-shadow:0 1px 3px #0000000f}.osl-rg-toolbar-btn--on{background:#eff6ff;border-color:#93c5fd;color:#1d4ed8}.osl-rg-toolbar-btn--active{background:#eff6ff;border-color:#93c5fd;color:#1d4ed8;font-weight:600}.osl-rg-toolbar-btn--danger{background:#fff5f5;border-color:#fca5a5;color:#dc2626}.osl-rg-toolbar-btn--danger:hover{background:#fee2e2;border-color:#f87171}.osl-rg-toolbar-btn--excel{background:linear-gradient(135deg,#1a7a3e,#1d6f42);border-color:#166534;color:#fff;font-weight:600;box-shadow:0 1px 3px #1665344d}.osl-rg-toolbar-btn--excel:hover{background:linear-gradient(135deg,#166534,#145a2c);border-color:#14532d;color:#fff;box-shadow:0 2px 6px #16653466}.osl-rg-global-search{position:relative;display:inline-flex;align-items:center}.osl-rg-global-search__icon{position:absolute;left:9px;width:14px;height:14px;color:#9ca3af;pointer-events:none;display:block}.osl-rg-global-search__input{height:32px;padding:0 30px;border:1px solid var(--osl-border-color, #e5e7eb);border-radius:7px;background:#fff;font-size:12.5px;font-family:inherit;color:#111827;outline:none;width:210px;transition:border-color .15s,width .2s,box-shadow .15s}.osl-rg-global-search__input::placeholder{color:#b0b7c3}.osl-rg-global-search__input:focus{border-color:var(--osl-primary, #2563eb);width:260px;box-shadow:0 0 0 3px #2563eb1a}.osl-rg-global-search__clear{position:absolute;right:6px;display:flex;align-items:center;justify-content:center;width:18px;height:18px;border:none;background:transparent;color:#9ca3af;cursor:pointer;padding:0;border-radius:4px;transition:all .12s}.osl-rg-global-search__clear svg{width:12px;height:12px}.osl-rg-global-search__clear:hover{color:#374151;background:#0000000d}.osl-rg-group-panel{display:flex;align-items:center;gap:8px;padding:6px 14px;background:linear-gradient(135deg,#fafbff,#f5f7ff);border-bottom:1px dashed #dde5ff;min-height:40px;flex-shrink:0;flex-wrap:wrap}.osl-rg-group-panel__label{display:flex;align-items:center;gap:5px;font-size:11px;font-weight:700;color:#6b7280;text-transform:uppercase;letter-spacing:.05em;white-space:nowrap;flex-shrink:0}.osl-rg-group-panel__label svg{color:#9ca3af}.osl-rg-group-panel__hint{font-size:11px;color:#b0b7c3;font-style:italic}.osl-rg-group-panel__clear{margin-left:auto;font-size:11px;color:#6b7280;background:none;border:none;cursor:pointer;text-decoration:underline;flex-shrink:0;padding:0;font-family:inherit}.osl-rg-group-panel__clear:hover{color:#dc2626}.osl-rg-group-chips{display:flex;gap:6px;flex-wrap:wrap;align-items:center;flex:1;min-height:26px}.osl-rg-group-chip{display:inline-flex;align-items:center;gap:5px;padding:3px 6px 3px 5px;background:linear-gradient(135deg,#dbeafe,#eff6ff);border:1px solid #bfdbfe;border-radius:20px;font-size:12px;font-weight:600;color:#1e40af;cursor:grab;-webkit-user-select:none;user-select:none;box-shadow:0 1px 2px #2563eb1a;transition:box-shadow .1s}.osl-rg-group-chip:active{cursor:grabbing;box-shadow:0 2px 6px #2563eb33}.osl-rg-group-chip .rg-drag-handle{color:#93c5fd}.osl-rg-group-chip__remove{display:flex;align-items:center;justify-content:center;width:16px;height:16px;border:none;background:transparent;cursor:pointer;padding:0;border-radius:50%;transition:all .1s}.osl-rg-group-chip__remove svg{width:10px;height:10px;color:#93c5fd}.osl-rg-group-chip__remove:hover{background:#dbeafe}.osl-rg-group-chip__remove:hover svg{color:#1e40af}.osl-rg-table-area{display:flex;flex-direction:column;flex:1 1 0;overflow:hidden;position:relative;min-height:0}.osl-rg-header-wrap{overflow:hidden;flex-shrink:0;border-bottom:2px solid #dde5ff;background:linear-gradient(180deg,#f0f4ff,#e8efff);z-index:10;position:relative}.osl-rg-header-row{display:flex;align-items:stretch}.osl-rg-header-cols{display:flex}.osl-rg-th{position:relative;display:flex;flex-direction:column;background:transparent;border-right:1px solid #dde5ff;cursor:pointer;flex-shrink:0;transition:background .12s;overflow:visible}.osl-rg-th:last-child{border-right:none}.osl-rg-th:hover{background:#2563eb0f}.osl-rg-th--pinned{background:linear-gradient(180deg,#e8efff,#dde8ff);border-right:1px solid #bfdbfe;z-index:3}.osl-rg-th--pinned:after{content:\"\";position:absolute;right:-5px;top:0;bottom:0;width:5px;background:linear-gradient(to right,rgba(37,99,235,.14),transparent);pointer-events:none;z-index:4}.osl-rg-th--sorted{background:#6366f114}.osl-rg-th--filtered{background:#f59e0b12}.osl-rg-th-drag-placeholder{background:#e0e7ff;border:2px dashed #6366f1;border-radius:4px;opacity:.6}.osl-rg-th-pin-badge{position:absolute;top:3px;right:24px;color:var(--osl-primary, #2563eb);opacity:.6;display:flex;align-items:center}.osl-rg-th-content{display:flex;align-items:center;justify-content:space-between;gap:4px;padding:0 8px 0 10px;height:36px;min-height:36px}.osl-rg-th-label{font-size:11.5px;font-weight:700;color:#1e3a6e;text-transform:uppercase;letter-spacing:.05em;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;flex:1;min-width:0}.osl-rg-th-actions{display:flex;align-items:center;gap:2px;flex-shrink:0}.osl-rg-sort-icon{display:inline-flex;align-items:center;position:relative;color:#9ca3af;transition:color .12s;width:14px;height:14px}.osl-rg-sort-icon svg{width:13px;height:13px;display:block}.osl-rg-sort-icon--asc,.osl-rg-sort-icon--desc{color:var(--osl-primary, #2563eb)}.osl-rg-sort-idle{opacity:.3}.osl-rg-sort-badge{position:absolute;top:-4px;right:-6px;font-size:8px;font-weight:700;background:var(--osl-primary, #2563eb);color:#fff;border-radius:50%;width:11px;height:11px;display:flex;align-items:center;justify-content:center;line-height:1}.osl-rg-filter-btn,.osl-rg-col-menu-btn{display:inline-flex;align-items:center;justify-content:center;width:22px;height:22px;border:none;background:transparent;color:#b0b7c3;cursor:pointer;padding:0;border-radius:5px;transition:all .12s}.osl-rg-filter-btn svg,.osl-rg-col-menu-btn svg{width:13px;height:13px;display:block}.osl-rg-filter-btn:hover,.osl-rg-col-menu-btn:hover{background:#00000012;color:#374151}.osl-rg-filter-btn--active{color:var(--osl-primary, #2563eb)!important;background:#2563eb1f!important}.osl-rg-col-search{position:relative;display:flex;align-items:center;padding:3px 6px;border-top:1px solid #e0e7ff;background:#ffffff80}.osl-rg-col-search__icon{position:absolute;left:10px;width:12px;height:12px;display:block;pointer-events:none;color:#c4cad6}.osl-rg-col-search__input{width:100%;height:23px;padding:0 18px 0 22px;border:1px solid #dde5ff;border-radius:5px;font-size:11px;font-family:inherit;background:#fff;color:#111827;outline:none;transition:border-color .12s,box-shadow .12s}.osl-rg-col-search__input::placeholder{color:#c4cad6}.osl-rg-col-search__input:focus{border-color:var(--osl-primary, #2563eb);box-shadow:0 0 0 2px #2563eb1a}.osl-rg-col-search__clear{position:absolute;right:8px;display:flex;align-items:center;width:14px;height:14px;border:none;background:transparent;color:#9ca3af;cursor:pointer;padding:0;border-radius:3px;transition:all .1s}.osl-rg-col-search__clear svg{width:10px;height:10px;display:block}.osl-rg-col-search__clear:hover{color:#374151;background:#0000000f}.osl-rg-resize-handle{position:absolute;right:0;top:0;bottom:0;width:6px;cursor:col-resize;z-index:5}.osl-rg-resize-handle:after{content:\"\";position:absolute;right:2px;top:15%;bottom:15%;width:2px;border-radius:2px;background:#a5b4fc;opacity:0;transition:opacity .15s}.osl-rg-resize-handle:hover:after{opacity:1}.osl-rg-th:hover .osl-rg-resize-handle:after{opacity:.4}.osl-rg-excel-filter{position:fixed;z-index:9999;width:250px;background:#fff;border:1px solid #e5e7eb;border-radius:10px;box-shadow:0 12px 32px #00000026,0 2px 8px #00000012;animation:rg-fade-up .14s ease;overflow:hidden}.osl-rg-excel-filter__header{display:flex;align-items:center;justify-content:space-between;padding:9px 12px;background:linear-gradient(135deg,#f0f4ff,#e8efff);border-bottom:1px solid #dde5ff}.osl-rg-excel-filter__title{display:flex;align-items:center;gap:6px;font-size:11px;font-weight:700;color:#1e3a6e;text-transform:uppercase;letter-spacing:.06em}.osl-rg-excel-filter__clear-btn{font-size:11px;color:#dc2626;background:none;border:none;cursor:pointer;font-weight:600;padding:2px 6px;border-radius:4px;font-family:inherit;transition:background .1s}.osl-rg-excel-filter__clear-btn:hover{background:#fee2e2}.osl-rg-excel-filter__search{padding:7px 8px;border-bottom:1px solid #f3f4f6;position:relative}.osl-rg-excel-filter__search-input{width:100%;height:28px;padding:0 8px 0 28px;border:1px solid #e5e7eb;border-radius:6px;font-size:12px;font-family:inherit;outline:none;box-sizing:border-box;transition:border-color .12s;color:#111827}.osl-rg-excel-filter__search-input::placeholder{color:#b0b7c3}.osl-rg-excel-filter__search-input:focus{border-color:var(--osl-primary, #2563eb)}.osl-rg-excel-filter__select-all{padding:0 8px;border-bottom:2px solid #f3f4f6;background:#fafbff}.osl-rg-excel-filter__list{max-height:210px;overflow-y:auto;padding:3px 8px}.osl-rg-excel-filter__list::-webkit-scrollbar{width:5px}.osl-rg-excel-filter__list::-webkit-scrollbar-thumb{background:#d1d5db;border-radius:4px}.osl-rg-excel-filter__list::-webkit-scrollbar-thumb:hover{background:#9ca3af}.osl-rg-excel-filter__item{display:flex;align-items:center;gap:8px;padding:5px 4px;font-size:12.5px;color:#374151;cursor:pointer;border-radius:5px;transition:background .1s}.osl-rg-excel-filter__item:hover{background:#f3f4f6}.osl-rg-excel-filter__item--all{font-weight:600;color:#1f2937;padding:6px 4px}.osl-rg-excel-filter__item-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1}.osl-rg-excel-filter__footer{display:flex;gap:6px;padding:8px 10px;border-top:1px solid #f3f4f6;background:#f9fafb}.osl-rg-excel-filter__apply{flex:1;height:30px;border:none;border-radius:6px;font-size:12px;font-family:inherit;font-weight:600;cursor:pointer;background:var(--osl-primary, #2563eb);color:#fff;transition:background .12s}.osl-rg-excel-filter__apply:hover{background:#1d4ed8}.osl-rg-excel-filter__cancel{height:30px;padding:0 14px;border:1px solid #e5e7eb;border-radius:6px;font-size:12px;font-family:inherit;font-weight:500;cursor:pointer;background:#fff;color:#6b7280;transition:all .12s}.osl-rg-excel-filter__cancel:hover{background:#f3f4f6;border-color:#9ca3af}.osl-rg-th--checkbox,.osl-rg-td--checkbox{display:flex;align-items:center;justify-content:center;width:36px;min-width:36px;flex-shrink:0;border-right:1px solid #dde5ff}.osl-rg-checkbox{width:14px;height:14px;cursor:pointer;accent-color:var(--osl-primary, #2563eb);flex-shrink:0}.osl-rg-body{overflow-x:auto;overflow-y:auto;flex-shrink:0}.osl-rg-body::-webkit-scrollbar{width:8px;height:8px}.osl-rg-body::-webkit-scrollbar-track{background:#f9fafb}.osl-rg-body::-webkit-scrollbar-thumb{background:#d1d5db;border-radius:4px}.osl-rg-body::-webkit-scrollbar-thumb:hover{background:#9ca3af}.osl-rg-body::-webkit-scrollbar-corner{background:#f9fafb}.osl-rg-inner{display:block}.osl-rg-row{display:flex;align-items:stretch;border-bottom:1px solid #f3f4f6;transition:background .08s;cursor:default}.osl-rg-row:hover{background:#f0f5ff!important}.osl-rg-row--striped{background:#fafbff}.osl-rg-row--selected{background:#eff6ff!important;border-bottom-color:#dbeafe}.osl-rg-row--skeleton{pointer-events:none}.osl-rg-td{display:flex;align-items:center;padding:0 12px;border-right:1px solid #f0f2f7;overflow:hidden;background:inherit;flex-shrink:0}.osl-rg-td:last-child{border-right:none}.osl-rg-td--pinned{background:#fff;border-right:1px solid #e0e7ff;z-index:2}.osl-rg-td--pinned:after{content:\"\";position:absolute;right:-5px;top:0;bottom:0;width:5px;background:linear-gradient(to right,rgba(37,99,235,.07),transparent);pointer-events:none}.osl-rg-cell-text{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;display:block;width:100%;font-size:13px;color:#1f2937;line-height:1.4}.rg-profit-pos .osl-rg-cell-text{color:#15803d;font-weight:600}.rg-profit-neg .osl-rg-cell-text{color:#dc2626;font-weight:600}.rg-status-delivered .osl-rg-cell-text{color:#15803d;font-weight:600}.rg-status-shipped .osl-rg-cell-text{color:#2563eb;font-weight:600}.rg-status-processing .osl-rg-cell-text{color:#d97706;font-weight:600}.rg-status-pending .osl-rg-cell-text{color:#6b7280;font-weight:500}.rg-status-cancelled .osl-rg-cell-text{color:#dc2626;font-weight:500;text-decoration:line-through;opacity:.75}.osl-rg-group-row{display:flex;align-items:center;gap:7px;border-bottom:1px solid #e5e7eb;font-weight:600;cursor:pointer;flex-shrink:0;transition:filter .1s}.osl-rg-group-row--l0{background:linear-gradient(135deg,#eff6ff,#dbeafe);color:#1e40af;border-left:4px solid #3b82f6}.osl-rg-group-row--l1{background:linear-gradient(135deg,#f5f3ff,#ede9fe);color:#5b21b6;border-left:4px solid #8b5cf6}.osl-rg-group-row--l2{background:linear-gradient(135deg,#fff7ed,#fef3c7);color:#92400e;border-left:4px solid #f59e0b}.osl-rg-group-row:hover{filter:brightness(.97)}.osl-rg-group-row__key{font-size:10.5px;font-weight:700;text-transform:uppercase;letter-spacing:.05em;opacity:.65;flex-shrink:0}.osl-rg-group-row__value{font-size:13px;font-weight:700;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.osl-rg-group-row__count{font-size:11px;font-weight:600;padding:2px 9px;border-radius:12px;background:#00000017;margin-right:12px;flex-shrink:0}.osl-rg-group-chevron{width:16px;height:16px;flex-shrink:0;opacity:.75;display:block}.osl-rg-aggregate-row{display:flex;align-items:center;border-top:2px solid #e5e7eb;background:linear-gradient(135deg,#f0fdf4,#f9fafb);flex-shrink:0;min-height:38px}.osl-rg-agg-cell{display:flex;align-items:center;gap:5px;padding:0 12px;border-right:1px solid #e5e7eb;height:38px;flex-shrink:0;overflow:hidden}.osl-rg-agg-cell--checkbox{width:36px;min-width:36px}.osl-rg-agg-label{font-size:9.5px;font-weight:800;color:#9ca3af;text-transform:uppercase;letter-spacing:.06em;flex-shrink:0;background:#e5e7eb;padding:1px 4px;border-radius:3px}.osl-rg-agg-value{font-size:12px;font-weight:700;color:#15803d;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.osl-rg-skel{height:12px;border-radius:4px;background:linear-gradient(90deg,#e5e7eb 25%,#f3f4f6,#e5e7eb 75%);background-size:200% 100%;animation:rg-pulse 1.5s ease-in-out infinite}.osl-rg-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:64px 20px;color:#9ca3af;text-align:center}.osl-rg-empty__icon{margin-bottom:18px;opacity:.7}.osl-rg-empty__title{font-size:15px;font-weight:600;color:#374151;margin:0 0 6px}.osl-rg-empty__sub{font-size:13px;color:#9ca3af;margin:0 0 18px;max-width:290px;line-height:1.5}.osl-rg-empty__action{height:34px;padding:0 18px;background:var(--osl-primary, #2563eb);color:#fff;border:none;border-radius:7px;font-size:13px;font-family:inherit;font-weight:600;cursor:pointer;transition:background .15s;box-shadow:0 2px 6px #2563eb4d}.osl-rg-empty__action:hover{background:#1d4ed8}.osl-rg-pagination{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:9px 14px;border-top:1px solid var(--osl-border-color, #e5e7eb);background:linear-gradient(135deg,#f8faff,#f3f4f6);flex-shrink:0;flex-wrap:wrap}.osl-rg-pagination__info{font-size:12px;color:#6b7280;min-width:160px;white-space:nowrap}.osl-rg-pagination__info strong{color:#1f2937;font-weight:600}.osl-rg-pagination__controls{display:flex;align-items:center;gap:3px}.osl-rg-pagination__size{display:flex;align-items:center;gap:7px;min-width:120px;justify-content:flex-end}.osl-rg-pagination__size-label{font-size:11.5px;color:#9ca3af;white-space:nowrap}.osl-rg-page-btn{display:inline-flex;align-items:center;justify-content:center;gap:3px;height:30px;padding:0 8px;border:1px solid var(--osl-border-color, #e5e7eb);border-radius:6px;background:#fff;color:#374151;font-size:12px;font-family:inherit;font-weight:500;cursor:pointer;transition:all .12s;white-space:nowrap}.osl-rg-page-btn svg{display:block}.osl-rg-page-btn:hover:not(:disabled){background:#f3f4f6;border-color:#9ca3af;box-shadow:0 1px 2px #0000000f}.osl-rg-page-btn:disabled{opacity:.35;cursor:not-allowed}.osl-rg-page-btn--num{min-width:30px;padding:0;font-size:12.5px}.osl-rg-page-btn--active{background:var(--osl-primary, #2563eb);border-color:var(--osl-primary, #2563eb);color:#fff;font-weight:700;box-shadow:0 2px 5px #2563eb59}.osl-rg-page-btn--active:hover:not(:disabled){background:#1d4ed8;border-color:#1d4ed8}.osl-rg-page-ellipsis{display:inline-flex;align-items:center;justify-content:center;width:26px;color:#9ca3af;font-size:13px;pointer-events:none}.osl-rg-page-size{height:30px;padding:0 8px;border:1px solid var(--osl-border-color, #e5e7eb);border-radius:6px;background:#fff;font-size:12px;font-family:inherit;color:#374151;cursor:pointer;outline:none;transition:border-color .15s}.osl-rg-page-size:focus{border-color:var(--osl-primary, #2563eb)}.osl-rg-col-menu{position:fixed;z-index:9999;min-width:210px;background:#fff;border:1px solid #e5e7eb;border-radius:11px;box-shadow:0 12px 36px #00000029,0 3px 10px #00000014;overflow:hidden;animation:rg-fade-up .13s cubic-bezier(.16,1,.3,1)}.osl-rg-col-menu__header{padding:9px 14px 7px;font-size:10.5px;font-weight:800;letter-spacing:.08em;text-transform:uppercase;color:#9ca3af;border-bottom:1px solid #f3f4f6;background:#fafbff}.osl-rg-col-menu__divider{height:1px;background:#f3f4f6;margin:2px 0}.osl-rg-col-menu__item{display:flex;align-items:center;gap:9px;width:100%;padding:9px 14px;background:transparent;border:none;border-left:3px solid transparent;border-bottom:none;text-align:left;font-size:13px;font-weight:500;font-family:inherit;color:#374151;cursor:pointer;transition:all .1s}.osl-rg-col-menu__item svg{color:#9ca3af;display:block}.osl-rg-col-menu__item:hover{background:#f0f4ff;color:var(--osl-primary, #2563eb);border-left-color:var(--osl-primary, #2563eb)}.osl-rg-col-menu__item:hover svg{color:var(--osl-primary, #2563eb)}.osl-rg-col-menu__item--danger:hover{background:#fff5f5;color:#dc2626;border-left-color:#dc2626}.osl-rg-col-menu__item--danger:hover svg{color:#dc2626}.osl-rg-config-backdrop{position:fixed;inset:0;z-index:9000;background:#0003;-webkit-backdrop-filter:blur(1px);backdrop-filter:blur(1px)}.osl-rg-config-panel{position:fixed;top:0;right:0;bottom:0;z-index:9001;width:300px;background:#fff;border-left:1px solid #e5e7eb;box-shadow:-6px 0 24px #00000021;display:flex;flex-direction:column;animation:rg-slide-in .2s cubic-bezier(.16,1,.3,1)}.osl-rg-config-panel__header{display:flex;align-items:center;justify-content:space-between;padding:14px 16px;border-bottom:1px solid #e5e7eb;background:linear-gradient(135deg,#f0f4ff,#e8efff);flex-shrink:0}.osl-rg-config-panel__title{display:flex;align-items:center;gap:8px;font-size:14px;font-weight:700;color:#1e3a6e}.osl-rg-config-panel__title svg{color:#3b82f6}.osl-rg-config-panel__close{display:flex;align-items:center;justify-content:center;width:30px;height:30px;border:none;background:transparent;color:#9ca3af;cursor:pointer;border-radius:7px;padding:0;transition:all .12s}.osl-rg-config-panel__close svg{width:16px;height:16px;display:block}.osl-rg-config-panel__close:hover{background:#00000012;color:#374151}.osl-rg-config-panel__actions{display:flex;gap:8px;padding:10px 14px;border-bottom:1px solid #f3f4f6;flex-shrink:0;background:#fafbff}.osl-rg-config-hint{padding:6px 16px;font-size:10.5px;color:#b0b7c3;font-style:italic;border-bottom:1px solid #f3f4f6;flex-shrink:0}.osl-rg-config-action{display:inline-flex;align-items:center;gap:5px;height:30px;padding:0 10px;border:1px solid #e5e7eb;border-radius:6px;background:#fff;color:#374151;font-size:12px;font-family:inherit;font-weight:500;cursor:pointer;transition:all .12s}.osl-rg-config-action svg{display:block}.osl-rg-config-action:hover{background:#f3f4f6;border-color:#9ca3af}.osl-rg-config-list{flex:1;overflow-y:auto;padding:6px 0}.osl-rg-config-list::-webkit-scrollbar{width:5px}.osl-rg-config-list::-webkit-scrollbar-thumb{background:#e5e7eb;border-radius:4px}.osl-rg-config-list::-webkit-scrollbar-thumb:hover{background:#d1d5db}.osl-rg-config-item{display:flex;align-items:center;gap:9px;padding:7px 14px;transition:background .1s;cursor:grab}.osl-rg-config-item:hover{background:#f9fafb}.osl-rg-config-item:active{cursor:grabbing}.osl-rg-config-item--hidden{opacity:.5}.osl-rg-config-item__label{flex:1;font-size:13px;color:#374151;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:500}.osl-rg-config-item__label--hidden{color:#9ca3af;font-style:italic}.osl-rg-config-item__pin-btn{display:flex;align-items:center;justify-content:center;width:24px;height:24px;border:1px solid #e5e7eb;background:#fff;color:#d1d5db;cursor:pointer;padding:0;border-radius:5px;transition:all .12s;flex-shrink:0}.osl-rg-config-item__pin-btn svg{display:block}.osl-rg-config-item__pin-btn:hover,.osl-rg-config-item__pin-btn--active{background:#eff6ff;border-color:#93c5fd;color:var(--osl-primary, #2563eb)}.osl-rg-copy-toast{position:fixed;bottom:24px;left:50%;transform:translate(-50%);z-index:99999;display:flex;align-items:center;gap:7px;background:#1f2937;color:#fff;font-size:12.5px;font-weight:500;padding:8px 18px;border-radius:22px;animation:rg-fade-up .2s ease;box-shadow:0 6px 16px #0000004d;pointer-events:none;white-space:nowrap;font-family:inherit}.osl-rg-copy-toast svg{flex-shrink:0;color:#4ade80}\n"], dependencies: [{ kind: "directive", type: i1$2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { 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.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { 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: i3$1.CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: i3$1.CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: i3$1.CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }, { kind: "directive", type: i3$1.CdkDragPlaceholder, selector: "ng-template[cdkDragPlaceholder]", inputs: ["data"] }, { kind: "pipe", type: i1$2.DecimalPipe, name: "number" }, { kind: "pipe", type: i1$2.UpperCasePipe, name: "uppercase" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3559
+ }
3560
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: OslReportGrid, decorators: [{
3561
+ type: Component,
3562
+ args: [{ selector: 'osl-report-grid', standalone: false, providers: [DatePipe, DecimalPipe], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"osl-rg\" (click)=\"$event.stopPropagation()\">\n\n <!-- \u2550\u2550 TOOLBAR \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n <div class=\"osl-rg-toolbar\">\n <div class=\"osl-rg-toolbar__left\">\n @if (title) { <span class=\"osl-rg-title\">{{ title }}</span> }\n @if (!loading) {\n @if (_filteredTotal !== datasource.length) {\n <span class=\"osl-rg-filter-badge\">\n <span class=\"osl-rg-filter-badge__dot\"></span>\n {{ _filteredTotal | number }} of {{ datasource.length | number }} rows\n </span>\n } @else {\n <span class=\"osl-rg-record-count\">{{ datasource.length | number }} rows</span>\n }\n }\n </div>\n <div class=\"osl-rg-toolbar__right\">\n\n <!-- Global search -->\n <div class=\"osl-rg-global-search\">\n <svg class=\"osl-rg-global-search__icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <circle cx=\"11\" cy=\"11\" r=\"8\"/><path d=\"m21 21-4.35-4.35\"/>\n </svg>\n <input class=\"osl-rg-global-search__input\" placeholder=\"Search all columns\u2026\"\n [(ngModel)]=\"globalSearch\" (ngModelChange)=\"currentPage=1; processData()\" />\n @if (globalSearch) {\n <button class=\"osl-rg-global-search__clear\" (click)=\"globalSearch=''; processData()\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\"><path d=\"M18 6 6 18M6 6l12 12\"/></svg>\n </button>\n }\n </div>\n\n <!-- Sort active badge -->\n @if (sortStates.size > 0) {\n <button class=\"osl-rg-toolbar-btn osl-rg-toolbar-btn--active\" (click)=\"clearSort()\" title=\"Clear sort\">\n <svg class=\"rg-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M11 5H4M11 9H4M11 13H4M18 4v16M15 7l3-3 3 3M15 17l3 3 3-3\"/>\n </svg>\n <span>{{ sortStates.size }} sort{{ sortStates.size > 1 ? 's' : '' }}</span>\n <svg class=\"rg-icon rg-icon--xs\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\"><path d=\"M18 6 6 18M6 6l12 12\"/></svg>\n </button>\n }\n\n <!-- Filter active badge -->\n @if (hasAnyFilter) {\n <button class=\"osl-rg-toolbar-btn osl-rg-toolbar-btn--danger\" (click)=\"clearAllFilters()\" title=\"Clear all filters\">\n <svg class=\"rg-icon\" viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M22 3H2l8 9.46V19l4 2V12.46z\"/></svg>\n <span>Filtered</span>\n <svg class=\"rg-icon rg-icon--xs\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\"><path d=\"M18 6 6 18M6 6l12 12\"/></svg>\n </button>\n }\n\n <!-- Group toggle -->\n <button class=\"osl-rg-toolbar-btn\" [class.osl-rg-toolbar-btn--on]=\"showGroupPanel\"\n (click)=\"showGroupPanel=!showGroupPanel\" title=\"Group panel\">\n <svg class=\"rg-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <rect x=\"2\" y=\"7\" width=\"20\" height=\"14\" rx=\"2\"/><path d=\"M16 3H8a2 2 0 0 0-2 2v2h12V5a2 2 0 0 0-2-2z\"/>\n </svg>\n <span>Group</span>\n </button>\n\n <!-- Expand / collapse when grouped -->\n @if (activeGroups.length > 0) {\n <button class=\"osl-rg-toolbar-btn\" (click)=\"expandAll()\" title=\"Expand all\">\n <svg class=\"rg-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"m7 20 5-5 5 5M7 4l5 5 5-5\"/></svg>\n </button>\n <button class=\"osl-rg-toolbar-btn\" (click)=\"collapseAll()\" title=\"Collapse all\">\n <svg class=\"rg-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"m7 15 5-5 5 5M7 9l5 5 5 5\"/></svg>\n </button>\n }\n\n <!-- Auto-size all -->\n <button class=\"osl-rg-toolbar-btn\" (click)=\"autoSizeAll()\" title=\"Auto-size all columns\">\n <svg class=\"rg-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M21 6H3M21 18H3\"/><path d=\"M8 3 3 6l5 3M16 3l5 3-5 3M8 15l-5 3 5 3M16 15l5 3-5 3\"/>\n </svg>\n <span>Auto-size</span>\n </button>\n\n <!-- Column config -->\n <button class=\"osl-rg-toolbar-btn\" [class.osl-rg-toolbar-btn--on]=\"showColumnConfig\"\n (click)=\"showColumnConfig=!showColumnConfig; $event.stopPropagation()\" title=\"Column settings\">\n <svg class=\"rg-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M4 6h16M4 12h16M4 18h16\"/>\n <circle cx=\"8\" cy=\"6\" r=\"2\" fill=\"white\" stroke=\"currentColor\" stroke-width=\"2\"/>\n <circle cx=\"16\" cy=\"12\" r=\"2\" fill=\"white\" stroke=\"currentColor\" stroke-width=\"2\"/>\n <circle cx=\"8\" cy=\"18\" r=\"2\" fill=\"white\" stroke=\"currentColor\" stroke-width=\"2\"/>\n </svg>\n <span>Columns</span>\n </button>\n\n <!-- Export Excel -->\n @if (exportable) {\n <button class=\"osl-rg-toolbar-btn osl-rg-toolbar-btn--excel\" (click)=\"exportCsv()\" title=\"Export to Excel (CSV)\">\n <svg class=\"rg-icon rg-icon--excel\" viewBox=\"0 0 24 24\">\n <rect width=\"24\" height=\"24\" rx=\"3\" fill=\"#1D6F42\"/>\n <path d=\"M13.5 3H7a1 1 0 0 0-1 1v16a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V7.5L13.5 3z\" fill=\"#fff\" opacity=\".15\"/>\n <path d=\"M13.5 3v4.5H18L13.5 3z\" fill=\"#fff\" opacity=\".3\"/>\n <path d=\"M8 9.5l2.3 3.5L8 16.5h1.6l1.4-2.3 1.4 2.3H14l-2.3-3.5 2.3-3.5h-1.6l-1.4 2.3-1.4-2.3H8z\" fill=\"#fff\"/>\n </svg>\n <span>Export</span>\n </button>\n }\n </div>\n </div>\n\n <!-- \u2550\u2550 GROUP PANEL \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n @if (showGroupPanel) {\n <div class=\"osl-rg-group-panel\">\n <span class=\"osl-rg-group-panel__label\">Group by:</span>\n @if (activeGroups.length === 0) {\n <span class=\"osl-rg-group-panel__hint\">Use the \u22EE column menu \u2192 \"Group by this\"</span>\n }\n <div class=\"osl-rg-group-chips\" cdkDropList cdkDropListOrientation=\"horizontal\"\n [cdkDropListData]=\"activeGroups\" (cdkDropListDropped)=\"onGroupPanelDrop($event)\">\n @for (key of activeGroups; track key) {\n <div class=\"osl-rg-group-chip\" cdkDrag>\n <svg cdkDragHandle class=\"rg-drag-handle\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <circle cx=\"9\" cy=\"5\" r=\"1.5\"/><circle cx=\"15\" cy=\"5\" r=\"1.5\"/>\n <circle cx=\"9\" cy=\"12\" r=\"1.5\"/><circle cx=\"15\" cy=\"12\" r=\"1.5\"/>\n <circle cx=\"9\" cy=\"19\" r=\"1.5\"/><circle cx=\"15\" cy=\"19\" r=\"1.5\"/>\n </svg>\n <span>{{ getGroupColLabel(key) }}</span>\n <button class=\"osl-rg-group-chip__remove\" (click)=\"removeGroup(key)\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\"><path d=\"M18 6 6 18M6 6l12 12\"/></svg>\n </button>\n </div>\n }\n </div>\n @if (activeGroups.length > 0) {\n <button class=\"osl-rg-group-panel__clear\" (click)=\"clearGroups()\">Clear groups</button>\n }\n </div>\n }\n\n <!-- \u2550\u2550 STICKY HEADER \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n <div class=\"osl-rg-header-wrap\" #headerScrollRef>\n <div class=\"osl-rg-header-row\" [style.minWidth.px]=\"totalWidth\">\n\n <!-- Select-all checkbox -->\n @if (rowSelection === 'multiple') {\n <div class=\"osl-rg-th osl-rg-th--checkbox\">\n <input type=\"checkbox\" class=\"osl-rg-checkbox\"\n [checked]=\"allSelected\" [indeterminate]=\"someSelected\"\n (change)=\"toggleSelectAll($any($event.target).checked)\" />\n </div>\n }\n\n <!-- Column headers (drag-to-reorder) -->\n <div class=\"osl-rg-header-cols\" cdkDropList cdkDropListOrientation=\"horizontal\"\n [cdkDropListData]=\"_cols\" (cdkDropListDropped)=\"onColumnReorder($event)\"\n style=\"display:flex;flex:1;min-width:0;\">\n\n @for (col of visibleCols; track col.key) {\n <div class=\"osl-rg-th\"\n [class.osl-rg-th--pinned]=\"col._pinned\"\n [class.osl-rg-th--sorted]=\"getSortState(col.key)\"\n [class.osl-rg-th--filtered]=\"hasActiveFilter(col.key)\"\n [style.width.px]=\"col._width\"\n [style.minWidth.px]=\"col._width\"\n [style.left.px]=\"col._pinned ? col._stickyLeft : null\"\n [style.position]=\"col._pinned ? 'sticky' : 'relative'\"\n [style.zIndex]=\"col._pinned ? 3 : 1\"\n cdkDrag [cdkDragDisabled]=\"col._pinned\"\n (cdkDragStarted)=\"onColumnDragStarted()\"\n (cdkDragEnded)=\"onColumnDragEnded()\"\n (click)=\"onHeaderClick(col, $event)\">\n\n <div *cdkDragPlaceholder class=\"osl-rg-th-drag-placeholder\"></div>\n\n <!-- Pin badge -->\n @if (col._pinned) {\n <span class=\"osl-rg-th-pin-badge\" title=\"Pinned\">\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" width=\"9\" height=\"9\"><path d=\"M16 12V4h1V2H7v2h1v8l-2 2v2h5.2v6h1.6v-6H18v-2l-2-2z\"/></svg>\n </span>\n }\n\n <!-- Label row -->\n <div class=\"osl-rg-th-content\">\n <span class=\"osl-rg-th-label\" [title]=\"col.label\">{{ col.label }}</span>\n <div class=\"osl-rg-th-actions\">\n\n <!-- Sort indicator -->\n @if (col.sortable) {\n <span class=\"osl-rg-sort-icon\"\n [class.osl-rg-sort-icon--asc]=\"getSortState(col.key)?.asc === true\"\n [class.osl-rg-sort-icon--desc]=\"getSortState(col.key)?.asc === false\">\n @if (getSortState(col.key); as s) {\n @if (s.asc) {\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M12 19V5m-7 7 7-7 7 7\"/></svg>\n } @else {\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M12 5v14m7-7-7 7-7-7\"/></svg>\n }\n @if (sortStates.size > 1) { <sup class=\"osl-rg-sort-badge\">{{ s.index + 1 }}</sup> }\n } @else {\n <svg class=\"osl-rg-sort-idle\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M8 9l4-4 4 4M16 15l-4 4-4-4\"/></svg>\n }\n </span>\n }\n\n <!-- Excel filter button -->\n @if (col.filterable) {\n <button class=\"osl-rg-filter-btn\" [class.osl-rg-filter-btn--active]=\"hasActiveFilter(col.key)\"\n (click)=\"openExcelFilter(col.key, $event)\" title=\"Filter\">\n @if (hasActiveFilter(col.key)) {\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M22 3H2l8 9.46V19l4 2V12.46z\"/></svg>\n } @else {\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polygon points=\"22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3\"/></svg>\n }\n </button>\n }\n\n <!-- Column menu -->\n <button class=\"osl-rg-col-menu-btn\" (click)=\"openColumnMenu(col.key, $event)\" title=\"Column options\">\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><circle cx=\"12\" cy=\"5\" r=\"2\"/><circle cx=\"12\" cy=\"12\" r=\"2\"/><circle cx=\"12\" cy=\"19\" r=\"2\"/></svg>\n </button>\n\n </div>\n </div>\n\n <!-- Per-column search -->\n @if (col.searchable) {\n <div class=\"osl-rg-col-search\" (click)=\"$event.stopPropagation()\">\n <svg class=\"osl-rg-col-search__icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"m18 18-3.5-3.5\"/>\n </svg>\n <input class=\"osl-rg-col-search__input\" [placeholder]=\"col.label + '\u2026'\"\n [ngModel]=\"columnSearch[col.key]\" (ngModelChange)=\"onColumnSearch(col.key, $event)\" />\n @if (columnSearch[col.key]) {\n <button class=\"osl-rg-col-search__clear\" (click)=\"columnSearch[col.key]=''; processData()\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\"><path d=\"M18 6 6 18M6 6l12 12\"/></svg>\n </button>\n }\n </div>\n }\n\n <!-- Resize handle -->\n @if (col.resizable) {\n <div class=\"osl-rg-resize-handle\" (mousedown)=\"startResize(col, $event)\" (dblclick)=\"autoSizeColumn(col, $event)\" (click)=\"$event.stopPropagation()\"></div>\n }\n\n\n </div><!-- /osl-rg-th -->\n }\n\n </div><!-- /header-cols -->\n </div><!-- /header-row -->\n </div><!-- /header-wrap -->\n\n <!-- \u2550\u2550 SCROLLABLE BODY \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n <div class=\"osl-rg-body\" [style.height]=\"tableHeight\" #bodyScrollRef (scroll)=\"onBodyScroll($event)\">\n <div class=\"osl-rg-inner\" [style.minWidth.px]=\"totalWidth\">\n\n <!-- Skeleton loading -->\n @if (loading) {\n @for (sk of skeletonRows; track $index) {\n <div class=\"osl-rg-row osl-rg-row--skeleton\" [style.height.px]=\"rowHeight\">\n @if (rowSelection === 'multiple') {\n <div class=\"osl-rg-td osl-rg-td--checkbox\"><div class=\"osl-rg-skel\" style=\"width:14px;height:14px;border-radius:3px;\"></div></div>\n }\n @for (col of visibleCols; track col.key) {\n <div class=\"osl-rg-td\" [style.width.px]=\"col._width\" [style.minWidth.px]=\"col._width\">\n <div class=\"osl-rg-skel\" [style.width]=\"$index % 3 === 0 ? '55%' : $index % 3 === 1 ? '80%' : '65%'\"></div>\n </div>\n }\n </div>\n }\n\n } @else if (isEmpty) {\n <!-- Empty state -->\n <div class=\"osl-rg-empty\">\n <div class=\"osl-rg-empty__icon\">\n <svg viewBox=\"0 0 48 48\" fill=\"none\" stroke=\"#d1d5db\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <rect x=\"6\" y=\"6\" width=\"36\" height=\"36\" rx=\"4\"/>\n <path d=\"M6 18h36M6 30h36M18 6v36\"/>\n </svg>\n </div>\n <p class=\"osl-rg-empty__title\">No records found</p>\n <p class=\"osl-rg-empty__sub\">\n @if (hasAnyFilter) { Try adjusting your filters or search terms }\n @else { No data to display }\n </p>\n @if (hasAnyFilter) {\n <button class=\"osl-rg-empty__action\" (click)=\"clearAllFilters()\">Clear all filters</button>\n }\n </div>\n\n } @else {\n <!-- Data rows -->\n @for (row of flatRows; track trackByRow($index, row)) {\n\n @if (row._type === 'group') {\n <div class=\"osl-rg-group-row\"\n [class.osl-rg-group-row--l0]=\"asGroupRow(row)._level === 0\"\n [class.osl-rg-group-row--l1]=\"asGroupRow(row)._level === 1\"\n [class.osl-rg-group-row--l2]=\"asGroupRow(row)._level >= 2\"\n [style.height.px]=\"rowHeight\"\n [style.paddingLeft.px]=\"asGroupRow(row)._level * 20 + 12\"\n (click)=\"toggleGroup(asGroupRow(row)._path)\">\n @if (asGroupRow(row)._expanded) {\n <svg class=\"osl-rg-group-chevron\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"m6 9 6 6 6-6\"/></svg>\n } @else {\n <svg class=\"osl-rg-group-chevron\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"m9 18 6-6-6-6\"/></svg>\n }\n <span class=\"osl-rg-group-row__key\">{{ asGroupRow(row)._colLabel }}:</span>\n <span class=\"osl-rg-group-row__value\">{{ asGroupRow(row)._label }}</span>\n <span class=\"osl-rg-group-row__count\">{{ asGroupRow(row)._count | number }} rows</span>\n </div>\n\n } @else {\n <div class=\"osl-rg-row\"\n [class.osl-rg-row--striped]=\"striped && asDataRow(row)._rowIndex % 2 === 1\"\n [class.osl-rg-row--selected]=\"selectedRows.has(asDataRow(row)._data)\"\n [style.height.px]=\"rowHeight\"\n (click)=\"onRowClick(asDataRow(row)._data, $event)\">\n\n @if (rowSelection === 'multiple') {\n <div class=\"osl-rg-td osl-rg-td--checkbox\">\n <input type=\"checkbox\" class=\"osl-rg-checkbox\"\n [checked]=\"selectedRows.has(asDataRow(row)._data)\"\n (change)=\"toggleRowSelect(asDataRow(row)._data, $event)\" />\n </div>\n }\n\n @for (col of visibleCols; track col.key) {\n <div class=\"osl-rg-td\"\n [class.osl-rg-td--pinned]=\"col._pinned\"\n [ngClass]=\"getCellClass(asDataRow(row)._data, col)\"\n [style.width.px]=\"col._width\"\n [style.minWidth.px]=\"col._width\"\n [style.left.px]=\"col._pinned ? col._stickyLeft : null\"\n [style.position]=\"col._pinned ? 'sticky' : 'relative'\"\n [style.zIndex]=\"col._pinned ? 2 : 0\"\n [style.textAlign]=\"col.align ?? 'left'\"\n (dblclick)=\"copyCell(getCellDisplay(asDataRow(row)._data, col), $event)\"\n [title]=\"getCellDisplay(asDataRow(row)._data, col)\">\n <span class=\"osl-rg-cell-text\">{{ getCellDisplay(asDataRow(row)._data, col) }}</span>\n </div>\n }\n\n </div>\n }\n\n }\n }\n\n </div><!-- /osl-rg-inner -->\n </div><!-- /osl-rg-body -->\n\n <!-- \u2550\u2550 AGGREGATE FOOTER \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n @if (showAggregates && hasAnyAggregate() && !loading) {\n <div class=\"osl-rg-aggregate-row\" [style.minWidth.px]=\"totalWidth\">\n @if (rowSelection === 'multiple') { <div class=\"osl-rg-agg-cell osl-rg-agg-cell--checkbox\"></div> }\n @for (col of visibleCols; track col.key) {\n <div class=\"osl-rg-agg-cell\" [style.width.px]=\"col._width\" [style.minWidth.px]=\"col._width\" [style.textAlign]=\"col.align ?? 'left'\">\n @if (col.aggregate) {\n <span class=\"osl-rg-agg-label\">{{ col.aggregate | uppercase }}</span>\n <span class=\"osl-rg-agg-value\">{{ getAggregate(col) }}</span>\n }\n </div>\n }\n </div>\n }\n\n <!-- \u2550\u2550 PAGINATION \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n @if (isPaginated) {\n <div class=\"osl-rg-pagination\">\n <span class=\"osl-rg-pagination__info\">\n @if (loading) { Loading\u2026 }\n @else if (_total > 0) { Showing <strong>{{ startRecord | number }}\u2013{{ endRecord | number }}</strong> of <strong>{{ _total | number }}</strong> records }\n @else { No records }\n </span>\n <div class=\"osl-rg-pagination__controls\">\n <button class=\"osl-rg-page-btn osl-rg-page-btn--nav\" (click)=\"goToPage(1)\" [disabled]=\"currentPage===1||loading\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" width=\"13\" height=\"13\"><path d=\"m11 17-5-5 5-5M18 17l-5-5 5-5\"/></svg>\n </button>\n <button class=\"osl-rg-page-btn osl-rg-page-btn--nav\" (click)=\"goToPage(currentPage-1)\" [disabled]=\"currentPage===1||loading\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" width=\"13\" height=\"13\"><path d=\"m15 18-6-6 6-6\"/></svg>\n Prev\n </button>\n @for (page of pageNumbers; track $index) {\n @if (page === -1) { <span class=\"osl-rg-page-ellipsis\">\u2026</span> }\n @else {\n <button class=\"osl-rg-page-btn osl-rg-page-btn--num\" [class.osl-rg-page-btn--active]=\"page===currentPage\"\n [disabled]=\"loading\" (click)=\"goToPage(page)\">{{ page }}</button>\n }\n }\n <button class=\"osl-rg-page-btn osl-rg-page-btn--nav\" (click)=\"goToPage(currentPage+1)\" [disabled]=\"currentPage===totalPages||loading\">\n Next\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" width=\"13\" height=\"13\"><path d=\"m9 18 6-6-6-6\"/></svg>\n </button>\n <button class=\"osl-rg-page-btn osl-rg-page-btn--nav\" (click)=\"goToPage(totalPages)\" [disabled]=\"currentPage===totalPages||loading\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" width=\"13\" height=\"13\"><path d=\"m13 17 5-5-5-5M6 17l5-5-5-5\"/></svg>\n </button>\n </div>\n <div class=\"osl-rg-pagination__size\">\n <span class=\"osl-rg-pagination__size-label\">Rows per page</span>\n <select class=\"osl-rg-page-size\" [ngModel]=\"pageSize\" (ngModelChange)=\"onPageSizeChange($event)\" [disabled]=\"loading\">\n @for (opt of pageSizeOptions; track opt) { <option [value]=\"opt\">{{ opt }}</option> }\n </select>\n </div>\n </div>\n }\n\n</div><!-- /osl-rg -->\n\n\n<!-- \u2550\u2550 EXCEL FILTER DROPDOWN (fixed, outside overflow containers) \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n@if (openFilterKey && excelFilterState[openFilterKey]) {\n <div class=\"osl-rg-excel-filter\"\n [style.top.px]=\"filterDropdownPos.top\"\n [style.left.px]=\"filterDropdownPos.left\"\n (click)=\"$event.stopPropagation()\">\n <div class=\"osl-rg-excel-filter__header\">\n <div class=\"osl-rg-excel-filter__title\">\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" width=\"12\" height=\"12\" style=\"flex-shrink:0;\"><path d=\"M22 3H2l8 9.46V19l4 2V12.46z\"/></svg>\n Filter: {{ getColumnByKey(openFilterKey)?.label }}\n </div>\n @if (hasActiveFilter(openFilterKey)) {\n <button class=\"osl-rg-excel-filter__clear-btn\" (click)=\"clearExcelFilter(openFilterKey!)\">Clear</button>\n }\n </div>\n <div class=\"osl-rg-excel-filter__search\">\n <input class=\"osl-rg-excel-filter__search-input\" placeholder=\"Search values\u2026\"\n [(ngModel)]=\"excelFilterState[openFilterKey].search\" />\n </div>\n <div class=\"osl-rg-excel-filter__select-all\">\n <label class=\"osl-rg-excel-filter__item osl-rg-excel-filter__item--all\">\n <input type=\"checkbox\" class=\"osl-rg-checkbox\" [checked]=\"isAllExcelChecked(openFilterKey)\"\n (change)=\"toggleAllExcelFilter(openFilterKey!, $any($event.target).checked)\" />\n <span class=\"osl-rg-excel-filter__item-label\">(Select All)</span>\n </label>\n </div>\n <div class=\"osl-rg-excel-filter__list\">\n @for (item of getFilteredExcelItems(openFilterKey); track item.value) {\n <label class=\"osl-rg-excel-filter__item\">\n <input type=\"checkbox\" class=\"osl-rg-checkbox\" [(ngModel)]=\"item.checked\" />\n <span class=\"osl-rg-excel-filter__item-label\">{{ item.label }}</span>\n </label>\n }\n </div>\n <div class=\"osl-rg-excel-filter__footer\">\n <button class=\"osl-rg-excel-filter__apply\" (click)=\"applyExcelFilter(openFilterKey!)\">Apply Filter</button>\n <button class=\"osl-rg-excel-filter__cancel\" (click)=\"openFilterKey=null\">Cancel</button>\n </div>\n </div>\n}\n\n\n<!-- \u2550\u2550 COLUMN CONTEXT MENU (fixed) \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n@if (columnMenuKey && getColumnByKey(columnMenuKey); as menuCol) {\n <div class=\"osl-rg-col-menu\"\n [style.top.px]=\"columnMenuPos.top\" [style.left.px]=\"columnMenuPos.left\"\n (click)=\"$event.stopPropagation()\">\n <div class=\"osl-rg-col-menu__header\">{{ menuCol.label }}</div>\n @if (menuCol.sortable) {\n <button class=\"osl-rg-col-menu__item\"\n (click)=\"sortStates.clear(); sortStates.set(menuCol.key,{key:menuCol.key,asc:true,index:0}); sortCounter=1; processData(); closeColumnMenu()\">\n <svg class=\"rg-icon rg-icon--sm\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M12 19V5m-7 7 7-7 7 7\"/></svg>\n Sort A \u2192 Z\n </button>\n <button class=\"osl-rg-col-menu__item\"\n (click)=\"sortStates.clear(); sortStates.set(menuCol.key,{key:menuCol.key,asc:false,index:0}); sortCounter=1; processData(); closeColumnMenu()\">\n <svg class=\"rg-icon rg-icon--sm\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M12 5v14m7-7-7 7-7-7\"/></svg>\n Sort Z \u2192 A\n </button>\n }\n @if (menuCol.groupable) {\n <div class=\"osl-rg-col-menu__divider\"></div>\n <button class=\"osl-rg-col-menu__item\" (click)=\"addGroup(menuCol.key); closeColumnMenu()\">\n <svg class=\"rg-icon rg-icon--sm\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"2\" y=\"7\" width=\"20\" height=\"14\" rx=\"2\"/><path d=\"M16 3H8a2 2 0 0 0-2 2v2h12V5a2 2 0 0 0-2-2z\"/></svg>\n Group by this\n </button>\n }\n <div class=\"osl-rg-col-menu__divider\"></div>\n <button class=\"osl-rg-col-menu__item\" (click)=\"toggleColumnPin(menuCol)\">\n <svg class=\"rg-icon rg-icon--sm\" viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M16 12V4h1V2H7v2h1v8l-2 2v2h5.2v6h1.6v-6H18v-2l-2-2z\"/></svg>\n {{ menuCol._pinned ? 'Unpin column' : 'Pin to left' }}\n </button>\n <button class=\"osl-rg-col-menu__item\" (click)=\"autoSizeColumn(menuCol); closeColumnMenu()\">\n <svg class=\"rg-icon rg-icon--sm\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M21 6H3M21 18H3\"/><path d=\"M8 3 3 6l5 3M16 3l5 3-5 3M8 15l-5 3 5 3M16 15l5 3-5 3\"/></svg>\n Auto-size column\n </button>\n <div class=\"osl-rg-col-menu__divider\"></div>\n <button class=\"osl-rg-col-menu__item osl-rg-col-menu__item--danger\" (click)=\"toggleColumnVisibility(menuCol); closeColumnMenu()\">\n <svg class=\"rg-icon rg-icon--sm\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24M1 1l22 22\"/>\n </svg>\n Hide column\n </button>\n </div>\n}\n\n\n<!-- \u2550\u2550 COLUMN CONFIG SIDE DRAWER \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n@if (showColumnConfig) {\n <div class=\"osl-rg-config-backdrop\" (click)=\"showColumnConfig=false\"></div>\n <div class=\"osl-rg-config-panel\" (click)=\"$event.stopPropagation()\">\n <div class=\"osl-rg-config-panel__header\">\n <div class=\"osl-rg-config-panel__title\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" width=\"16\" height=\"16\">\n <path d=\"M4 6h16M4 12h16M4 18h16\"/>\n <circle cx=\"8\" cy=\"6\" r=\"2\" fill=\"white\" stroke=\"currentColor\" stroke-width=\"2\"/>\n <circle cx=\"16\" cy=\"12\" r=\"2\" fill=\"white\" stroke=\"currentColor\" stroke-width=\"2\"/>\n <circle cx=\"8\" cy=\"18\" r=\"2\" fill=\"white\" stroke=\"currentColor\" stroke-width=\"2\"/>\n </svg>\n Column Settings\n </div>\n <button class=\"osl-rg-config-panel__close\" (click)=\"showColumnConfig=false\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\"><path d=\"M18 6 6 18M6 6l12 12\"/></svg>\n </button>\n </div>\n <div class=\"osl-rg-config-panel__actions\">\n <button class=\"osl-rg-config-action\" (click)=\"autoSizeAll()\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" width=\"13\" height=\"13\"><path d=\"M21 6H3M21 18H3\"/><path d=\"M8 3 3 6l5 3M16 3l5 3-5 3\"/></svg>\n Auto-size all\n </button>\n <button class=\"osl-rg-config-action\" (click)=\"showAllColumns()\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" width=\"13\" height=\"13\"><path d=\"M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z\"/><circle cx=\"12\" cy=\"12\" r=\"3\"/></svg>\n Show all\n </button>\n </div>\n <div class=\"osl-rg-config-hint\">Drag to reorder \u00B7 Toggle to show/hide \u00B7 Pin to freeze</div>\n <div class=\"osl-rg-config-list\" cdkDropList (cdkDropListDropped)=\"onConfigColumnReorder($event)\">\n @for (col of _cols; track col.key) {\n <div class=\"osl-rg-config-item\" cdkDrag [class.osl-rg-config-item--hidden]=\"col._hidden\">\n <svg cdkDragHandle class=\"rg-drag-handle\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <circle cx=\"9\" cy=\"5\" r=\"1.5\"/><circle cx=\"15\" cy=\"5\" r=\"1.5\"/>\n <circle cx=\"9\" cy=\"12\" r=\"1.5\"/><circle cx=\"15\" cy=\"12\" r=\"1.5\"/>\n <circle cx=\"9\" cy=\"19\" r=\"1.5\"/><circle cx=\"15\" cy=\"19\" r=\"1.5\"/>\n </svg>\n <input type=\"checkbox\" class=\"osl-rg-checkbox\" [checked]=\"!col._hidden\" (change)=\"toggleColumnVisibility(col)\" />\n <span class=\"osl-rg-config-item__label\" [class.osl-rg-config-item__label--hidden]=\"col._hidden\">{{ col.label }}</span>\n <button class=\"osl-rg-config-item__pin-btn\" (click)=\"toggleColumnPin(col)\"\n [class.osl-rg-config-item__pin-btn--active]=\"col._pinned\"\n [title]=\"col._pinned ? 'Unpin' : 'Pin to left'\">\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" width=\"12\" height=\"12\"><path d=\"M16 12V4h1V2H7v2h1v8l-2 2v2h5.2v6h1.6v-6H18v-2l-2-2z\"/></svg>\n </button>\n </div>\n }\n </div>\n </div>\n}\n\n\n<!-- \u2550\u2550 COPY TOAST \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n@if (copiedCell !== null) {\n <div class=\"osl-rg-copy-toast\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" width=\"13\" height=\"13\"><polyline points=\"20 6 9 17 4 12\"/></svg>\n Copied to clipboard\n </div>\n}\n", styles: ["@keyframes rg-pulse{0%,to{opacity:1}50%{opacity:.4}}@keyframes rg-fade-up{0%{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}@keyframes rg-slide-in{0%{opacity:0;transform:translate(20px)}to{opacity:1;transform:translate(0)}}.rg-icon{width:15px;height:15px;flex-shrink:0;display:block}.rg-icon--sm{width:14px;height:14px}.rg-icon--xs{width:12px;height:12px}.rg-icon--excel{width:18px;height:18px}.rg-drag-handle{width:14px;height:14px;flex-shrink:0;color:#d1d5db;cursor:grab;display:block}.rg-drag-handle:active{cursor:grabbing}.osl-rg{position:relative;display:flex;flex-direction:column;width:100%;font-family:inherit;font-size:13px;color:#111827;background:#fff;border:1px solid var(--osl-border-color, #e5e7eb);border-radius:10px;overflow:hidden;box-shadow:0 2px 8px #00000012,0 1px 2px #0000000a;-webkit-user-select:none;user-select:none}.osl-rg-toolbar{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:10px 14px;background:linear-gradient(135deg,#f8faff,#f3f4f6);border-bottom:1px solid var(--osl-border-color, #e5e7eb);flex-shrink:0;flex-wrap:wrap}.osl-rg-toolbar__left{display:flex;align-items:center;gap:10px;flex-wrap:wrap}.osl-rg-toolbar__right{display:flex;align-items:center;gap:6px;flex-wrap:wrap}.osl-rg-title{font-size:14px;font-weight:700;color:#111827;letter-spacing:-.01em}.osl-rg-record-count{display:inline-flex;align-items:center;gap:5px;font-size:12px;color:#9ca3af;font-weight:500}.osl-rg-record-count svg{color:#d1d5db}.osl-rg-filter-badge{display:inline-flex;align-items:center;gap:5px;padding:2px 9px 2px 6px;background:#eff6ff;border:1px solid #bfdbfe;border-radius:12px;font-size:11px;color:#1d4ed8;font-weight:600}.osl-rg-filter-badge__dot{width:6px;height:6px;border-radius:50%;background:#2563eb;animation:rg-pulse 1.5s ease-in-out infinite;flex-shrink:0}.osl-rg-toolbar-btn{display:inline-flex;align-items:center;gap:5px;height:32px;padding:0 11px;border:1px solid var(--osl-border-color, #e5e7eb);border-radius:7px;background:#fff;color:#5a6478;font-size:12px;font-family:inherit;font-weight:500;cursor:pointer;white-space:nowrap;transition:all .15s ease}.osl-rg-toolbar-btn svg{flex-shrink:0}.osl-rg-toolbar-btn:hover{background:#f3f4f6;color:#1f2937;border-color:#c4c9d4;box-shadow:0 1px 3px #0000000f}.osl-rg-toolbar-btn--on{background:#eff6ff;border-color:#93c5fd;color:#1d4ed8}.osl-rg-toolbar-btn--active{background:#eff6ff;border-color:#93c5fd;color:#1d4ed8;font-weight:600}.osl-rg-toolbar-btn--danger{background:#fff5f5;border-color:#fca5a5;color:#dc2626}.osl-rg-toolbar-btn--danger:hover{background:#fee2e2;border-color:#f87171}.osl-rg-toolbar-btn--excel{background:linear-gradient(135deg,#1a7a3e,#1d6f42);border-color:#166534;color:#fff;font-weight:600;box-shadow:0 1px 3px #1665344d}.osl-rg-toolbar-btn--excel:hover{background:linear-gradient(135deg,#166534,#145a2c);border-color:#14532d;color:#fff;box-shadow:0 2px 6px #16653466}.osl-rg-global-search{position:relative;display:inline-flex;align-items:center}.osl-rg-global-search__icon{position:absolute;left:9px;width:14px;height:14px;color:#9ca3af;pointer-events:none;display:block}.osl-rg-global-search__input{height:32px;padding:0 30px;border:1px solid var(--osl-border-color, #e5e7eb);border-radius:7px;background:#fff;font-size:12.5px;font-family:inherit;color:#111827;outline:none;width:210px;transition:border-color .15s,width .2s,box-shadow .15s}.osl-rg-global-search__input::placeholder{color:#b0b7c3}.osl-rg-global-search__input:focus{border-color:var(--osl-primary, #2563eb);width:260px;box-shadow:0 0 0 3px #2563eb1a}.osl-rg-global-search__clear{position:absolute;right:6px;display:flex;align-items:center;justify-content:center;width:18px;height:18px;border:none;background:transparent;color:#9ca3af;cursor:pointer;padding:0;border-radius:4px;transition:all .12s}.osl-rg-global-search__clear svg{width:12px;height:12px}.osl-rg-global-search__clear:hover{color:#374151;background:#0000000d}.osl-rg-group-panel{display:flex;align-items:center;gap:8px;padding:6px 14px;background:linear-gradient(135deg,#fafbff,#f5f7ff);border-bottom:1px dashed #dde5ff;min-height:40px;flex-shrink:0;flex-wrap:wrap}.osl-rg-group-panel__label{display:flex;align-items:center;gap:5px;font-size:11px;font-weight:700;color:#6b7280;text-transform:uppercase;letter-spacing:.05em;white-space:nowrap;flex-shrink:0}.osl-rg-group-panel__label svg{color:#9ca3af}.osl-rg-group-panel__hint{font-size:11px;color:#b0b7c3;font-style:italic}.osl-rg-group-panel__clear{margin-left:auto;font-size:11px;color:#6b7280;background:none;border:none;cursor:pointer;text-decoration:underline;flex-shrink:0;padding:0;font-family:inherit}.osl-rg-group-panel__clear:hover{color:#dc2626}.osl-rg-group-chips{display:flex;gap:6px;flex-wrap:wrap;align-items:center;flex:1;min-height:26px}.osl-rg-group-chip{display:inline-flex;align-items:center;gap:5px;padding:3px 6px 3px 5px;background:linear-gradient(135deg,#dbeafe,#eff6ff);border:1px solid #bfdbfe;border-radius:20px;font-size:12px;font-weight:600;color:#1e40af;cursor:grab;-webkit-user-select:none;user-select:none;box-shadow:0 1px 2px #2563eb1a;transition:box-shadow .1s}.osl-rg-group-chip:active{cursor:grabbing;box-shadow:0 2px 6px #2563eb33}.osl-rg-group-chip .rg-drag-handle{color:#93c5fd}.osl-rg-group-chip__remove{display:flex;align-items:center;justify-content:center;width:16px;height:16px;border:none;background:transparent;cursor:pointer;padding:0;border-radius:50%;transition:all .1s}.osl-rg-group-chip__remove svg{width:10px;height:10px;color:#93c5fd}.osl-rg-group-chip__remove:hover{background:#dbeafe}.osl-rg-group-chip__remove:hover svg{color:#1e40af}.osl-rg-table-area{display:flex;flex-direction:column;flex:1 1 0;overflow:hidden;position:relative;min-height:0}.osl-rg-header-wrap{overflow:hidden;flex-shrink:0;border-bottom:2px solid #dde5ff;background:linear-gradient(180deg,#f0f4ff,#e8efff);z-index:10;position:relative}.osl-rg-header-row{display:flex;align-items:stretch}.osl-rg-header-cols{display:flex}.osl-rg-th{position:relative;display:flex;flex-direction:column;background:transparent;border-right:1px solid #dde5ff;cursor:pointer;flex-shrink:0;transition:background .12s;overflow:visible}.osl-rg-th:last-child{border-right:none}.osl-rg-th:hover{background:#2563eb0f}.osl-rg-th--pinned{background:linear-gradient(180deg,#e8efff,#dde8ff);border-right:1px solid #bfdbfe;z-index:3}.osl-rg-th--pinned:after{content:\"\";position:absolute;right:-5px;top:0;bottom:0;width:5px;background:linear-gradient(to right,rgba(37,99,235,.14),transparent);pointer-events:none;z-index:4}.osl-rg-th--sorted{background:#6366f114}.osl-rg-th--filtered{background:#f59e0b12}.osl-rg-th-drag-placeholder{background:#e0e7ff;border:2px dashed #6366f1;border-radius:4px;opacity:.6}.osl-rg-th-pin-badge{position:absolute;top:3px;right:24px;color:var(--osl-primary, #2563eb);opacity:.6;display:flex;align-items:center}.osl-rg-th-content{display:flex;align-items:center;justify-content:space-between;gap:4px;padding:0 8px 0 10px;height:36px;min-height:36px}.osl-rg-th-label{font-size:11.5px;font-weight:700;color:#1e3a6e;text-transform:uppercase;letter-spacing:.05em;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;flex:1;min-width:0}.osl-rg-th-actions{display:flex;align-items:center;gap:2px;flex-shrink:0}.osl-rg-sort-icon{display:inline-flex;align-items:center;position:relative;color:#9ca3af;transition:color .12s;width:14px;height:14px}.osl-rg-sort-icon svg{width:13px;height:13px;display:block}.osl-rg-sort-icon--asc,.osl-rg-sort-icon--desc{color:var(--osl-primary, #2563eb)}.osl-rg-sort-idle{opacity:.3}.osl-rg-sort-badge{position:absolute;top:-4px;right:-6px;font-size:8px;font-weight:700;background:var(--osl-primary, #2563eb);color:#fff;border-radius:50%;width:11px;height:11px;display:flex;align-items:center;justify-content:center;line-height:1}.osl-rg-filter-btn,.osl-rg-col-menu-btn{display:inline-flex;align-items:center;justify-content:center;width:22px;height:22px;border:none;background:transparent;color:#b0b7c3;cursor:pointer;padding:0;border-radius:5px;transition:all .12s}.osl-rg-filter-btn svg,.osl-rg-col-menu-btn svg{width:13px;height:13px;display:block}.osl-rg-filter-btn:hover,.osl-rg-col-menu-btn:hover{background:#00000012;color:#374151}.osl-rg-filter-btn--active{color:var(--osl-primary, #2563eb)!important;background:#2563eb1f!important}.osl-rg-col-search{position:relative;display:flex;align-items:center;padding:3px 6px;border-top:1px solid #e0e7ff;background:#ffffff80}.osl-rg-col-search__icon{position:absolute;left:10px;width:12px;height:12px;display:block;pointer-events:none;color:#c4cad6}.osl-rg-col-search__input{width:100%;height:23px;padding:0 18px 0 22px;border:1px solid #dde5ff;border-radius:5px;font-size:11px;font-family:inherit;background:#fff;color:#111827;outline:none;transition:border-color .12s,box-shadow .12s}.osl-rg-col-search__input::placeholder{color:#c4cad6}.osl-rg-col-search__input:focus{border-color:var(--osl-primary, #2563eb);box-shadow:0 0 0 2px #2563eb1a}.osl-rg-col-search__clear{position:absolute;right:8px;display:flex;align-items:center;width:14px;height:14px;border:none;background:transparent;color:#9ca3af;cursor:pointer;padding:0;border-radius:3px;transition:all .1s}.osl-rg-col-search__clear svg{width:10px;height:10px;display:block}.osl-rg-col-search__clear:hover{color:#374151;background:#0000000f}.osl-rg-resize-handle{position:absolute;right:0;top:0;bottom:0;width:6px;cursor:col-resize;z-index:5}.osl-rg-resize-handle:after{content:\"\";position:absolute;right:2px;top:15%;bottom:15%;width:2px;border-radius:2px;background:#a5b4fc;opacity:0;transition:opacity .15s}.osl-rg-resize-handle:hover:after{opacity:1}.osl-rg-th:hover .osl-rg-resize-handle:after{opacity:.4}.osl-rg-excel-filter{position:fixed;z-index:9999;width:250px;background:#fff;border:1px solid #e5e7eb;border-radius:10px;box-shadow:0 12px 32px #00000026,0 2px 8px #00000012;animation:rg-fade-up .14s ease;overflow:hidden}.osl-rg-excel-filter__header{display:flex;align-items:center;justify-content:space-between;padding:9px 12px;background:linear-gradient(135deg,#f0f4ff,#e8efff);border-bottom:1px solid #dde5ff}.osl-rg-excel-filter__title{display:flex;align-items:center;gap:6px;font-size:11px;font-weight:700;color:#1e3a6e;text-transform:uppercase;letter-spacing:.06em}.osl-rg-excel-filter__clear-btn{font-size:11px;color:#dc2626;background:none;border:none;cursor:pointer;font-weight:600;padding:2px 6px;border-radius:4px;font-family:inherit;transition:background .1s}.osl-rg-excel-filter__clear-btn:hover{background:#fee2e2}.osl-rg-excel-filter__search{padding:7px 8px;border-bottom:1px solid #f3f4f6;position:relative}.osl-rg-excel-filter__search-input{width:100%;height:28px;padding:0 8px 0 28px;border:1px solid #e5e7eb;border-radius:6px;font-size:12px;font-family:inherit;outline:none;box-sizing:border-box;transition:border-color .12s;color:#111827}.osl-rg-excel-filter__search-input::placeholder{color:#b0b7c3}.osl-rg-excel-filter__search-input:focus{border-color:var(--osl-primary, #2563eb)}.osl-rg-excel-filter__select-all{padding:0 8px;border-bottom:2px solid #f3f4f6;background:#fafbff}.osl-rg-excel-filter__list{max-height:210px;overflow-y:auto;padding:3px 8px}.osl-rg-excel-filter__list::-webkit-scrollbar{width:5px}.osl-rg-excel-filter__list::-webkit-scrollbar-thumb{background:#d1d5db;border-radius:4px}.osl-rg-excel-filter__list::-webkit-scrollbar-thumb:hover{background:#9ca3af}.osl-rg-excel-filter__item{display:flex;align-items:center;gap:8px;padding:5px 4px;font-size:12.5px;color:#374151;cursor:pointer;border-radius:5px;transition:background .1s}.osl-rg-excel-filter__item:hover{background:#f3f4f6}.osl-rg-excel-filter__item--all{font-weight:600;color:#1f2937;padding:6px 4px}.osl-rg-excel-filter__item-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1}.osl-rg-excel-filter__footer{display:flex;gap:6px;padding:8px 10px;border-top:1px solid #f3f4f6;background:#f9fafb}.osl-rg-excel-filter__apply{flex:1;height:30px;border:none;border-radius:6px;font-size:12px;font-family:inherit;font-weight:600;cursor:pointer;background:var(--osl-primary, #2563eb);color:#fff;transition:background .12s}.osl-rg-excel-filter__apply:hover{background:#1d4ed8}.osl-rg-excel-filter__cancel{height:30px;padding:0 14px;border:1px solid #e5e7eb;border-radius:6px;font-size:12px;font-family:inherit;font-weight:500;cursor:pointer;background:#fff;color:#6b7280;transition:all .12s}.osl-rg-excel-filter__cancel:hover{background:#f3f4f6;border-color:#9ca3af}.osl-rg-th--checkbox,.osl-rg-td--checkbox{display:flex;align-items:center;justify-content:center;width:36px;min-width:36px;flex-shrink:0;border-right:1px solid #dde5ff}.osl-rg-checkbox{width:14px;height:14px;cursor:pointer;accent-color:var(--osl-primary, #2563eb);flex-shrink:0}.osl-rg-body{overflow-x:auto;overflow-y:auto;flex-shrink:0}.osl-rg-body::-webkit-scrollbar{width:8px;height:8px}.osl-rg-body::-webkit-scrollbar-track{background:#f9fafb}.osl-rg-body::-webkit-scrollbar-thumb{background:#d1d5db;border-radius:4px}.osl-rg-body::-webkit-scrollbar-thumb:hover{background:#9ca3af}.osl-rg-body::-webkit-scrollbar-corner{background:#f9fafb}.osl-rg-inner{display:block}.osl-rg-row{display:flex;align-items:stretch;border-bottom:1px solid #f3f4f6;transition:background .08s;cursor:default}.osl-rg-row:hover{background:#f0f5ff!important}.osl-rg-row--striped{background:#fafbff}.osl-rg-row--selected{background:#eff6ff!important;border-bottom-color:#dbeafe}.osl-rg-row--skeleton{pointer-events:none}.osl-rg-td{display:flex;align-items:center;padding:0 12px;border-right:1px solid #f0f2f7;overflow:hidden;background:inherit;flex-shrink:0}.osl-rg-td:last-child{border-right:none}.osl-rg-td--pinned{background:#fff;border-right:1px solid #e0e7ff;z-index:2}.osl-rg-td--pinned:after{content:\"\";position:absolute;right:-5px;top:0;bottom:0;width:5px;background:linear-gradient(to right,rgba(37,99,235,.07),transparent);pointer-events:none}.osl-rg-cell-text{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;display:block;width:100%;font-size:13px;color:#1f2937;line-height:1.4}.rg-profit-pos .osl-rg-cell-text{color:#15803d;font-weight:600}.rg-profit-neg .osl-rg-cell-text{color:#dc2626;font-weight:600}.rg-status-delivered .osl-rg-cell-text{color:#15803d;font-weight:600}.rg-status-shipped .osl-rg-cell-text{color:#2563eb;font-weight:600}.rg-status-processing .osl-rg-cell-text{color:#d97706;font-weight:600}.rg-status-pending .osl-rg-cell-text{color:#6b7280;font-weight:500}.rg-status-cancelled .osl-rg-cell-text{color:#dc2626;font-weight:500;text-decoration:line-through;opacity:.75}.osl-rg-group-row{display:flex;align-items:center;gap:7px;border-bottom:1px solid #e5e7eb;font-weight:600;cursor:pointer;flex-shrink:0;transition:filter .1s}.osl-rg-group-row--l0{background:linear-gradient(135deg,#eff6ff,#dbeafe);color:#1e40af;border-left:4px solid #3b82f6}.osl-rg-group-row--l1{background:linear-gradient(135deg,#f5f3ff,#ede9fe);color:#5b21b6;border-left:4px solid #8b5cf6}.osl-rg-group-row--l2{background:linear-gradient(135deg,#fff7ed,#fef3c7);color:#92400e;border-left:4px solid #f59e0b}.osl-rg-group-row:hover{filter:brightness(.97)}.osl-rg-group-row__key{font-size:10.5px;font-weight:700;text-transform:uppercase;letter-spacing:.05em;opacity:.65;flex-shrink:0}.osl-rg-group-row__value{font-size:13px;font-weight:700;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.osl-rg-group-row__count{font-size:11px;font-weight:600;padding:2px 9px;border-radius:12px;background:#00000017;margin-right:12px;flex-shrink:0}.osl-rg-group-chevron{width:16px;height:16px;flex-shrink:0;opacity:.75;display:block}.osl-rg-aggregate-row{display:flex;align-items:center;border-top:2px solid #e5e7eb;background:linear-gradient(135deg,#f0fdf4,#f9fafb);flex-shrink:0;min-height:38px}.osl-rg-agg-cell{display:flex;align-items:center;gap:5px;padding:0 12px;border-right:1px solid #e5e7eb;height:38px;flex-shrink:0;overflow:hidden}.osl-rg-agg-cell--checkbox{width:36px;min-width:36px}.osl-rg-agg-label{font-size:9.5px;font-weight:800;color:#9ca3af;text-transform:uppercase;letter-spacing:.06em;flex-shrink:0;background:#e5e7eb;padding:1px 4px;border-radius:3px}.osl-rg-agg-value{font-size:12px;font-weight:700;color:#15803d;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.osl-rg-skel{height:12px;border-radius:4px;background:linear-gradient(90deg,#e5e7eb 25%,#f3f4f6,#e5e7eb 75%);background-size:200% 100%;animation:rg-pulse 1.5s ease-in-out infinite}.osl-rg-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:64px 20px;color:#9ca3af;text-align:center}.osl-rg-empty__icon{margin-bottom:18px;opacity:.7}.osl-rg-empty__title{font-size:15px;font-weight:600;color:#374151;margin:0 0 6px}.osl-rg-empty__sub{font-size:13px;color:#9ca3af;margin:0 0 18px;max-width:290px;line-height:1.5}.osl-rg-empty__action{height:34px;padding:0 18px;background:var(--osl-primary, #2563eb);color:#fff;border:none;border-radius:7px;font-size:13px;font-family:inherit;font-weight:600;cursor:pointer;transition:background .15s;box-shadow:0 2px 6px #2563eb4d}.osl-rg-empty__action:hover{background:#1d4ed8}.osl-rg-pagination{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:9px 14px;border-top:1px solid var(--osl-border-color, #e5e7eb);background:linear-gradient(135deg,#f8faff,#f3f4f6);flex-shrink:0;flex-wrap:wrap}.osl-rg-pagination__info{font-size:12px;color:#6b7280;min-width:160px;white-space:nowrap}.osl-rg-pagination__info strong{color:#1f2937;font-weight:600}.osl-rg-pagination__controls{display:flex;align-items:center;gap:3px}.osl-rg-pagination__size{display:flex;align-items:center;gap:7px;min-width:120px;justify-content:flex-end}.osl-rg-pagination__size-label{font-size:11.5px;color:#9ca3af;white-space:nowrap}.osl-rg-page-btn{display:inline-flex;align-items:center;justify-content:center;gap:3px;height:30px;padding:0 8px;border:1px solid var(--osl-border-color, #e5e7eb);border-radius:6px;background:#fff;color:#374151;font-size:12px;font-family:inherit;font-weight:500;cursor:pointer;transition:all .12s;white-space:nowrap}.osl-rg-page-btn svg{display:block}.osl-rg-page-btn:hover:not(:disabled){background:#f3f4f6;border-color:#9ca3af;box-shadow:0 1px 2px #0000000f}.osl-rg-page-btn:disabled{opacity:.35;cursor:not-allowed}.osl-rg-page-btn--num{min-width:30px;padding:0;font-size:12.5px}.osl-rg-page-btn--active{background:var(--osl-primary, #2563eb);border-color:var(--osl-primary, #2563eb);color:#fff;font-weight:700;box-shadow:0 2px 5px #2563eb59}.osl-rg-page-btn--active:hover:not(:disabled){background:#1d4ed8;border-color:#1d4ed8}.osl-rg-page-ellipsis{display:inline-flex;align-items:center;justify-content:center;width:26px;color:#9ca3af;font-size:13px;pointer-events:none}.osl-rg-page-size{height:30px;padding:0 8px;border:1px solid var(--osl-border-color, #e5e7eb);border-radius:6px;background:#fff;font-size:12px;font-family:inherit;color:#374151;cursor:pointer;outline:none;transition:border-color .15s}.osl-rg-page-size:focus{border-color:var(--osl-primary, #2563eb)}.osl-rg-col-menu{position:fixed;z-index:9999;min-width:210px;background:#fff;border:1px solid #e5e7eb;border-radius:11px;box-shadow:0 12px 36px #00000029,0 3px 10px #00000014;overflow:hidden;animation:rg-fade-up .13s cubic-bezier(.16,1,.3,1)}.osl-rg-col-menu__header{padding:9px 14px 7px;font-size:10.5px;font-weight:800;letter-spacing:.08em;text-transform:uppercase;color:#9ca3af;border-bottom:1px solid #f3f4f6;background:#fafbff}.osl-rg-col-menu__divider{height:1px;background:#f3f4f6;margin:2px 0}.osl-rg-col-menu__item{display:flex;align-items:center;gap:9px;width:100%;padding:9px 14px;background:transparent;border:none;border-left:3px solid transparent;border-bottom:none;text-align:left;font-size:13px;font-weight:500;font-family:inherit;color:#374151;cursor:pointer;transition:all .1s}.osl-rg-col-menu__item svg{color:#9ca3af;display:block}.osl-rg-col-menu__item:hover{background:#f0f4ff;color:var(--osl-primary, #2563eb);border-left-color:var(--osl-primary, #2563eb)}.osl-rg-col-menu__item:hover svg{color:var(--osl-primary, #2563eb)}.osl-rg-col-menu__item--danger:hover{background:#fff5f5;color:#dc2626;border-left-color:#dc2626}.osl-rg-col-menu__item--danger:hover svg{color:#dc2626}.osl-rg-config-backdrop{position:fixed;inset:0;z-index:9000;background:#0003;-webkit-backdrop-filter:blur(1px);backdrop-filter:blur(1px)}.osl-rg-config-panel{position:fixed;top:0;right:0;bottom:0;z-index:9001;width:300px;background:#fff;border-left:1px solid #e5e7eb;box-shadow:-6px 0 24px #00000021;display:flex;flex-direction:column;animation:rg-slide-in .2s cubic-bezier(.16,1,.3,1)}.osl-rg-config-panel__header{display:flex;align-items:center;justify-content:space-between;padding:14px 16px;border-bottom:1px solid #e5e7eb;background:linear-gradient(135deg,#f0f4ff,#e8efff);flex-shrink:0}.osl-rg-config-panel__title{display:flex;align-items:center;gap:8px;font-size:14px;font-weight:700;color:#1e3a6e}.osl-rg-config-panel__title svg{color:#3b82f6}.osl-rg-config-panel__close{display:flex;align-items:center;justify-content:center;width:30px;height:30px;border:none;background:transparent;color:#9ca3af;cursor:pointer;border-radius:7px;padding:0;transition:all .12s}.osl-rg-config-panel__close svg{width:16px;height:16px;display:block}.osl-rg-config-panel__close:hover{background:#00000012;color:#374151}.osl-rg-config-panel__actions{display:flex;gap:8px;padding:10px 14px;border-bottom:1px solid #f3f4f6;flex-shrink:0;background:#fafbff}.osl-rg-config-hint{padding:6px 16px;font-size:10.5px;color:#b0b7c3;font-style:italic;border-bottom:1px solid #f3f4f6;flex-shrink:0}.osl-rg-config-action{display:inline-flex;align-items:center;gap:5px;height:30px;padding:0 10px;border:1px solid #e5e7eb;border-radius:6px;background:#fff;color:#374151;font-size:12px;font-family:inherit;font-weight:500;cursor:pointer;transition:all .12s}.osl-rg-config-action svg{display:block}.osl-rg-config-action:hover{background:#f3f4f6;border-color:#9ca3af}.osl-rg-config-list{flex:1;overflow-y:auto;padding:6px 0}.osl-rg-config-list::-webkit-scrollbar{width:5px}.osl-rg-config-list::-webkit-scrollbar-thumb{background:#e5e7eb;border-radius:4px}.osl-rg-config-list::-webkit-scrollbar-thumb:hover{background:#d1d5db}.osl-rg-config-item{display:flex;align-items:center;gap:9px;padding:7px 14px;transition:background .1s;cursor:grab}.osl-rg-config-item:hover{background:#f9fafb}.osl-rg-config-item:active{cursor:grabbing}.osl-rg-config-item--hidden{opacity:.5}.osl-rg-config-item__label{flex:1;font-size:13px;color:#374151;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:500}.osl-rg-config-item__label--hidden{color:#9ca3af;font-style:italic}.osl-rg-config-item__pin-btn{display:flex;align-items:center;justify-content:center;width:24px;height:24px;border:1px solid #e5e7eb;background:#fff;color:#d1d5db;cursor:pointer;padding:0;border-radius:5px;transition:all .12s;flex-shrink:0}.osl-rg-config-item__pin-btn svg{display:block}.osl-rg-config-item__pin-btn:hover,.osl-rg-config-item__pin-btn--active{background:#eff6ff;border-color:#93c5fd;color:var(--osl-primary, #2563eb)}.osl-rg-copy-toast{position:fixed;bottom:24px;left:50%;transform:translate(-50%);z-index:99999;display:flex;align-items:center;gap:7px;background:#1f2937;color:#fff;font-size:12.5px;font-weight:500;padding:8px 18px;border-radius:22px;animation:rg-fade-up .2s ease;box-shadow:0 6px 16px #0000004d;pointer-events:none;white-space:nowrap;font-family:inherit}.osl-rg-copy-toast svg{flex-shrink:0;color:#4ade80}\n"] }]
3563
+ }], propDecorators: { columns: [{
3564
+ type: Input
3565
+ }], datasource: [{
3566
+ type: Input
3567
+ }], loading: [{
3568
+ type: Input
3569
+ }], totalRecords: [{
3570
+ type: Input
3571
+ }], autoMode: [{
3572
+ type: Input
3573
+ }], isPaginated: [{
3574
+ type: Input
3575
+ }], pageSize: [{
3576
+ type: Input
3577
+ }], tableHeight: [{
3578
+ type: Input
3579
+ }], striped: [{
3580
+ type: Input
3581
+ }], exportable: [{
3582
+ type: Input
3583
+ }], rowHeight: [{
3584
+ type: Input
3585
+ }], rowSelection: [{
3586
+ type: Input
3587
+ }], showAggregates: [{
3588
+ type: Input
3589
+ }], title: [{
3590
+ type: Input
3591
+ }], pageChange: [{
3592
+ type: Output
3593
+ }], pageSizeChange: [{
3594
+ type: Output
3595
+ }], sortChange: [{
3596
+ type: Output
3597
+ }], rowClick: [{
3598
+ type: Output
3599
+ }], selectionChange: [{
3600
+ type: Output
3601
+ }], headerScrollRef: [{
3602
+ type: ViewChild,
3603
+ args: ['headerScrollRef']
3604
+ }], bodyScrollRef: [{
3605
+ type: ViewChild,
3606
+ args: ['bodyScrollRef']
3607
+ }], onMouseMove: [{
3608
+ type: HostListener,
3609
+ args: ['document:mousemove', ['$event']]
3610
+ }], onMouseUp: [{
3611
+ type: HostListener,
3612
+ args: ['document:mouseup']
3613
+ }], onDocumentClick: [{
3614
+ type: HostListener,
3615
+ args: ['document:click']
3616
+ }] } });
3617
+
3618
+ class OslReportForm {
3619
+ cd;
3620
+ title = 'Report';
3621
+ formElements = [];
3622
+ _model = {};
3623
+ set model(val) { this._model = val ?? {}; }
3624
+ get model() { return this._model; }
3625
+ modelChange = new EventEmitter();
3626
+ generateOptions = ['pdf', 'grid', 'excel'];
3627
+ reportColumns = [];
3628
+ onGenerate;
3629
+ skeletonLoading = false;
3630
+ skeletonTheme = 'light';
3631
+ pageSize = 50;
3632
+ selectedType = 'grid';
3633
+ generating = false;
3634
+ resultDatasource = [];
3635
+ showResultView = false;
3636
+ constructor(cd) {
3637
+ this.cd = cd;
3638
+ }
3639
+ ngOnInit() {
3640
+ if (this.generateOptions.length > 0 && !this.generateOptions.includes(this.selectedType)) {
3641
+ this.selectedType = this.generateOptions[0];
3642
+ }
3643
+ }
3644
+ selectType(type) {
3645
+ this.selectedType = type;
3646
+ }
3647
+ async generate() {
3648
+ if (!this.onGenerate)
3649
+ return;
3650
+ this.generating = true;
3651
+ try {
3652
+ const result = await Promise.resolve(this.onGenerate({ ...this._model }, this.selectedType));
3653
+ if (result && result.length > 0) {
3654
+ this.resultDatasource = result || [];
3655
+ if (this.selectedType === 'grid') {
3656
+ this.showResultView = true;
3657
+ }
3658
+ }
3659
+ }
3660
+ finally {
3661
+ this.generating = false;
3662
+ }
3663
+ this.cd.markForCheck();
3664
+ }
3665
+ backToForm() {
3666
+ this.showResultView = false;
3667
+ this.resultDatasource = [];
3668
+ }
3669
+ reExportExcel() { this._exportCsv(this.resultDatasource); }
3670
+ reExportPdf() { this._exportPdf(this.resultDatasource); }
3671
+ typeLabel(type) {
3672
+ return type === 'pdf' ? 'PDF' : type === 'excel' ? 'Excel' : 'Grid';
3673
+ }
3674
+ typeIcon(type) {
3675
+ return type === 'pdf' ? 'picture_as_pdf' : type === 'excel' ? 'table_chart' : 'grid_on';
3676
+ }
3677
+ onModelChange(val) {
3678
+ this._model = val;
3679
+ this.modelChange.emit(val);
3680
+ }
3681
+ _exportCsv(data) {
3682
+ if (!data.length || !this.reportColumns.length)
3683
+ return;
3684
+ const cols = this.reportColumns;
3685
+ const header = cols.map(c => `"${c.label}"`).join(',');
3686
+ const rows = data.map(row => cols.map(col => `"${String(row[col.key] ?? '').replace(/"/g, '""')}"`).join(','));
3687
+ const blob = new Blob([[header, ...rows].join('\n')], { type: 'text/csv;charset=utf-8;' });
3688
+ const a = Object.assign(document.createElement('a'), {
3689
+ href: URL.createObjectURL(blob),
3690
+ download: `${this.title || 'report'}.csv`,
3691
+ });
3692
+ a.click();
3693
+ URL.revokeObjectURL(a.href);
3694
+ }
3695
+ _exportPdf(data) {
3696
+ if (!data.length || !this.reportColumns.length)
3697
+ return;
3698
+ const cols = this.reportColumns;
3699
+ const headerRow = cols.map(c => `<th>${c.label}</th>`).join('');
3700
+ const bodyRows = data.map(row => `<tr>${cols.map(col => `<td>${String(row[col.key] ?? '')}</td>`).join('')}</tr>`).join('');
3701
+ const w = window.open('', '_blank');
3702
+ if (!w)
3703
+ return;
3704
+ w.document.write(`<!DOCTYPE html><html><head><title>${this.title}</title>
3705
+ <style>
3706
+ body{font-family:Arial,sans-serif;padding:24px;font-size:13px;color:#111827}
3707
+ h2{color:#1e3a8a;margin:0 0 4px;font-size:20px;font-weight:700}
3708
+ .subtitle{color:#6b7280;font-size:12px;margin-bottom:20px}
3709
+ table{border-collapse:collapse;width:100%}
3710
+ th{background:#1e3a8a;color:#fff;padding:9px 12px;text-align:left;font-weight:600;font-size:12px}
3711
+ td{padding:8px 12px;border-bottom:1px solid #e5e7eb;font-size:12px}
3712
+ tr:nth-child(even) td{background:#f8fafc}
3713
+ @media print{@page{margin:.8cm}}
3714
+ </style>
3715
+ </head><body>
3716
+ <h2>${this.title}</h2>
3717
+ <p class="subtitle">Generated on ${new Date().toLocaleString()}</p>
3718
+ <table><thead><tr>${headerRow}</tr></thead><tbody>${bodyRows}</tbody></table>
3719
+ <script>window.onload=function(){window.print();}<\/script>
3720
+ </body></html>`);
3721
+ w.document.close();
3722
+ }
3723
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: OslReportForm, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
3724
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: OslReportForm, isStandalone: false, selector: "osl-report-form", inputs: { title: "title", formElements: "formElements", model: "model", generateOptions: "generateOptions", reportColumns: "reportColumns", onGenerate: "onGenerate", skeletonLoading: "skeletonLoading", skeletonTheme: "skeletonTheme", pageSize: "pageSize" }, outputs: { modelChange: "modelChange" }, ngImport: i0, template: "<!-- \u2500\u2500 Form view (hidden when results are shown) \u2500\u2500\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 (!showResultView) {\n <div class=\"orf-card\">\n\n <!-- Header -->\n <div class=\"orf-header\">\n <div class=\"orf-header__left\">\n <div class=\"orf-header__icon-wrap\">\n <mat-icon class=\"orf-header__icon\">assessment</mat-icon>\n </div>\n <div class=\"orf-header__text\">\n <h4 class=\"orf-header__title\">{{ title }}</h4>\n <p class=\"orf-header__sub\">Configure parameters and generate report</p>\n </div>\n </div>\n\n <div class=\"orf-header__right\">\n @for (type of generateOptions; track type) {\n <button\n class=\"orf-type-btn\"\n [class.active]=\"selectedType === type\"\n [class.orf-type-btn--pdf]=\"type === 'pdf'\"\n [class.orf-type-btn--excel]=\"type === 'excel'\"\n [class.orf-type-btn--grid]=\"type === 'grid'\"\n (click)=\"selectType(type)\"\n type=\"button\"\n >\n <mat-icon>{{ typeIcon(type) }}</mat-icon>\n <span>{{ typeLabel(type) }}</span>\n </button>\n }\n </div>\n </div>\n\n <!-- Divider -->\n <div class=\"orf-divider\"></div>\n\n <!-- Form Body -->\n <div class=\"orf-body\">\n <osl-dynamic-form\n [elements]=\"formElements\"\n [model]=\"model\"\n (modelChange)=\"onModelChange($event)\"\n [skeletonLoading]=\"skeletonLoading\"\n [skeletonTheme]=\"skeletonTheme\"\n ></osl-dynamic-form>\n </div>\n\n <!-- Footer -->\n <div class=\"orf-footer\">\n <button\n class=\"orf-generate-btn\"\n (click)=\"generate()\"\n [disabled]=\"generating\"\n type=\"button\"\n >\n @if (generating) {\n <span class=\"orf-generate-btn__spinner\"></span>\n <span>Generating...</span>\n } @else {\n <mat-icon>{{ typeIcon(selectedType) }}</mat-icon>\n <span>Generate {{ typeLabel(selectedType) }}</span>\n <mat-icon class=\"orf-generate-btn__arrow\">arrow_forward</mat-icon>\n }\n </button>\n </div>\n\n </div>\n}\n\n<!-- \u2500\u2500 Full-screen results 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 -->\n@if (showResultView) {\n <!-- <div class=\"orf-results-view\"> -->\n\n <!-- Results header bar -->\n <div class=\"orf-results-header\">\n\n <div class=\"orf-results-header__left\">\n <button class=\"orf-back-btn\" (click)=\"backToForm()\" type=\"button\">\n <mat-icon>arrow_back</mat-icon>\n <span>Back</span>\n </button>\n <div class=\"orf-results-header__sep\"></div>\n <mat-icon class=\"orf-results-header__report-icon\">assessment</mat-icon>\n <span class=\"orf-results-header__title\">{{ title }}</span>\n <span class=\"orf-results-badge\">\n <mat-icon>table_rows</mat-icon>\n {{ resultDatasource.length | number }} records\n </span>\n </div>\n\n <div class=\"orf-results-header__right\">\n @if (generateOptions.includes('excel')) {\n <button class=\"orf-reexport-btn orf-reexport-btn--excel\" (click)=\"reExportExcel()\" type=\"button\">\n <mat-icon>table_chart</mat-icon>\n <span>Excel</span>\n </button>\n }\n @if (generateOptions.includes('pdf')) {\n <button class=\"orf-reexport-btn orf-reexport-btn--pdf\" (click)=\"reExportPdf()\" type=\"button\">\n <mat-icon>picture_as_pdf</mat-icon>\n <span>PDF</span>\n </button>\n }\n </div>\n\n </div>\n\n <!-- Report grid -->\n <div class=\"orf-results-body\">\n <osl-report-grid\n [columns]=\"reportColumns\"\n [datasource]=\"resultDatasource\"\n [loading]=\"generating\"\n [title]=\"title\"\n tableHeight=\"calc(100vh - 200px)\"\n [pageSize]=\"pageSize\"\n [exportable]=\"true\"\n [showAggregates]=\"true\"\n [striped]=\"true\"\n ></osl-report-grid>\n </div>\n\n <!-- </div> -->\n}\n", styles: ["@keyframes orf-fade-in{0%{opacity:0;transform:translateY(12px)}to{opacity:1;transform:translateY(0)}}@keyframes orf-slide-in{0%{opacity:0;transform:translate(-10px)}to{opacity:1;transform:translate(0)}}@keyframes orf-spin{to{transform:rotate(360deg)}}.orf-card{background:#fff;border-radius:12px;border:1px solid #e5e7eb;box-shadow:0 4px 20px #00000014,0 1px 4px #0000000a;overflow:hidden}.orf-header{background:linear-gradient(135deg,#1e3a8a,#2563eb 55%,#60a5fa);padding:22px 24px;display:flex;align-items:center;justify-content:space-between;gap:16px;position:relative;overflow:hidden;flex-wrap:wrap}.orf-header:before{content:\"\";position:absolute;width:260px;height:260px;border-radius:50%;background:#ffffff0d;top:-110px;right:10px;pointer-events:none}.orf-header:after{content:\"\";position:absolute;width:160px;height:160px;border-radius:50%;background:#ffffff12;bottom:-70px;right:200px;pointer-events:none}.orf-header__left{display:flex;align-items:center;gap:14px;position:relative;z-index:1}.orf-header__icon-wrap{width:50px;height:50px;border-radius:14px;background:#ffffff26;border:1px solid rgba(255,255,255,.22);display:flex;align-items:center;justify-content:center;flex-shrink:0;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px)}.orf-header__icon{color:#fff;font-size:26px;width:26px;height:26px}.orf-header__text{display:flex;flex-direction:column;gap:3px}.orf-header__title{margin:0;font-size:19px;font-weight:700;color:#fff;letter-spacing:-.015em;line-height:1.2}.orf-header__sub{margin:0;font-size:12px;color:#ffffffad}.orf-header__right{display:flex;align-items:center;gap:8px;position:relative;z-index:1;flex-wrap:wrap}.orf-type-btn{display:inline-flex;align-items:center;gap:6px;padding:8px 16px;border-radius:8px;border:1px solid rgba(255,255,255,.28);background:#ffffff1f;color:#ffffffd1;font-size:13px;font-weight:500;cursor:pointer;transition:all .2s ease;white-space:nowrap;-webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px);font-family:inherit}.orf-type-btn mat-icon{font-size:16px;width:16px;height:16px}.orf-type-btn:hover{background:#ffffff38;border-color:#ffffff73;color:#fff;transform:translateY(-1px)}.orf-type-btn.active{background:#fffffff2;border-color:transparent;color:#1e3a8a;box-shadow:0 4px 14px #00000038;transform:translateY(-1px);font-weight:600}.orf-type-btn.active mat-icon{color:#1e3a8a}.orf-type-btn--pdf.active{color:#dc2626}.orf-type-btn--pdf.active mat-icon{color:#dc2626}.orf-type-btn--excel.active{color:#16a34a}.orf-type-btn--excel.active mat-icon{color:#16a34a}.orf-type-btn--grid.active{color:#2563eb}.orf-type-btn--grid.active mat-icon{color:#2563eb}.orf-divider{height:1px;background:linear-gradient(90deg,#e5e7eb,#c7d2fe,#e5e7eb)}.orf-body{padding:20px 20px 4px}.orf-footer{padding:12px 20px 20px;display:flex;justify-content:flex-end}.orf-generate-btn{display:inline-flex;align-items:center;gap:8px;padding:10px 22px;border-radius:8px;border:none;background:linear-gradient(135deg,#1e3a8a,#2563eb);color:#fff;font-size:14px;font-weight:600;cursor:pointer;transition:all .25s ease;box-shadow:0 4px 16px #2563eb52;letter-spacing:.01em;font-family:inherit}.orf-generate-btn mat-icon{font-size:18px;width:18px;height:18px}.orf-generate-btn__arrow{transition:transform .2s ease;font-size:16px!important;width:16px!important;height:16px!important;margin-left:2px}.orf-generate-btn:hover:not([disabled]){background:linear-gradient(135deg,#1e40af,#1d4ed8);box-shadow:0 6px 22px #2563eb7a;transform:translateY(-1px)}.orf-generate-btn:hover:not([disabled]) .orf-generate-btn__arrow{transform:translate(4px)}.orf-generate-btn:active:not([disabled]){transform:translateY(0);box-shadow:0 2px 8px #2563eb38}.orf-generate-btn[disabled]{opacity:.72;cursor:not-allowed;box-shadow:none}.orf-generate-btn__spinner{width:16px;height:16px;border:2px solid rgba(255,255,255,.3);border-top-color:#fff;border-radius:50%;display:inline-block;animation:orf-spin .7s linear infinite;flex-shrink:0}.orf-results-view{position:fixed;inset:0;z-index:200;display:flex;flex-direction:column;background:#f1f5f9;animation:orf-fade-in .25s ease}.orf-results-header{flex-shrink:0;height:56px;background:linear-gradient(135deg,#1e3a8a,#2563eb 60%,#3b82f6);display:flex;align-items:center;justify-content:space-between;padding:0 16px;gap:12px;position:relative;overflow:hidden;box-shadow:0 2px 12px #2563eb66}.orf-results-header:after{content:\"\";position:absolute;width:180px;height:180px;border-radius:50%;background:#ffffff0f;top:-80px;right:60px;pointer-events:none}.orf-results-header__left{display:flex;align-items:center;gap:10px;position:relative;z-index:1;min-width:0;flex:1}.orf-results-header__sep{width:1px;height:22px;background:#ffffff40;flex-shrink:0}.orf-results-header__report-icon{color:#ffffffd9;font-size:20px;width:20px;height:20px;flex-shrink:0}.orf-results-header__title{font-size:15px;font-weight:700;color:#fff;letter-spacing:-.01em;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.orf-results-badge{display:inline-flex;align-items:center;gap:4px;padding:3px 10px;border-radius:20px;background:#ffffff26;border:1px solid rgba(255,255,255,.2);color:#ffffffe6;font-size:11px;font-weight:600;white-space:nowrap;flex-shrink:0;animation:orf-slide-in .3s ease}.orf-results-badge mat-icon{font-size:13px;width:13px;height:13px}.orf-back-btn{display:inline-flex;align-items:center;gap:5px;padding:6px 14px;border-radius:7px;border:1px solid rgba(255,255,255,.3);background:#ffffff24;color:#fff;font-size:13px;font-weight:500;cursor:pointer;transition:all .18s ease;-webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px);font-family:inherit;flex-shrink:0}.orf-back-btn mat-icon{font-size:17px;width:17px;height:17px;transition:transform .18s ease}.orf-back-btn:hover{background:#ffffff3d;border-color:#ffffff80}.orf-back-btn:hover mat-icon{transform:translate(-3px)}.orf-back-btn:active{transform:scale(.97)}.orf-results-header__right{display:flex;align-items:center;gap:8px;position:relative;z-index:1;flex-shrink:0}.orf-reexport-btn{display:inline-flex;align-items:center;gap:5px;padding:6px 13px;border-radius:7px;border:1px solid rgba(255,255,255,.28);background:#ffffff1f;color:#ffffffd9;font-size:12px;font-weight:500;cursor:pointer;transition:all .18s ease;-webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px);font-family:inherit;white-space:nowrap}.orf-reexport-btn mat-icon{font-size:15px;width:15px;height:15px}.orf-reexport-btn:hover{background:#ffffff38;border-color:#ffffff73;color:#fff;transform:translateY(-1px)}.orf-reexport-btn--excel:hover{background:#16a34a59;border-color:#16a34a80}.orf-reexport-btn--pdf:hover{background:#dc262659;border-color:#dc262680}.orf-results-body{flex:1;overflow:hidden;padding:12px 12px 0;display:flex;flex-direction:column}.orf-results-body osl-report-grid{flex:1;display:block}\n"], dependencies: [{ kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: DynamicForm, selector: "osl-dynamic-form", inputs: ["elements", "model", "skeletonLoading", "skeletonTheme"], outputs: ["modelChange"] }, { kind: "component", type: OslReportGrid, selector: "osl-report-grid", inputs: ["columns", "datasource", "loading", "totalRecords", "autoMode", "isPaginated", "pageSize", "tableHeight", "striped", "exportable", "rowHeight", "rowSelection", "showAggregates", "title"], outputs: ["pageChange", "pageSizeChange", "sortChange", "rowClick", "selectionChange"] }, { kind: "pipe", type: i1$2.DecimalPipe, name: "number" }] });
3725
+ }
3726
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: OslReportForm, decorators: [{
3727
+ type: Component,
3728
+ args: [{ selector: 'osl-report-form', standalone: false, template: "<!-- \u2500\u2500 Form view (hidden when results are shown) \u2500\u2500\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 (!showResultView) {\n <div class=\"orf-card\">\n\n <!-- Header -->\n <div class=\"orf-header\">\n <div class=\"orf-header__left\">\n <div class=\"orf-header__icon-wrap\">\n <mat-icon class=\"orf-header__icon\">assessment</mat-icon>\n </div>\n <div class=\"orf-header__text\">\n <h4 class=\"orf-header__title\">{{ title }}</h4>\n <p class=\"orf-header__sub\">Configure parameters and generate report</p>\n </div>\n </div>\n\n <div class=\"orf-header__right\">\n @for (type of generateOptions; track type) {\n <button\n class=\"orf-type-btn\"\n [class.active]=\"selectedType === type\"\n [class.orf-type-btn--pdf]=\"type === 'pdf'\"\n [class.orf-type-btn--excel]=\"type === 'excel'\"\n [class.orf-type-btn--grid]=\"type === 'grid'\"\n (click)=\"selectType(type)\"\n type=\"button\"\n >\n <mat-icon>{{ typeIcon(type) }}</mat-icon>\n <span>{{ typeLabel(type) }}</span>\n </button>\n }\n </div>\n </div>\n\n <!-- Divider -->\n <div class=\"orf-divider\"></div>\n\n <!-- Form Body -->\n <div class=\"orf-body\">\n <osl-dynamic-form\n [elements]=\"formElements\"\n [model]=\"model\"\n (modelChange)=\"onModelChange($event)\"\n [skeletonLoading]=\"skeletonLoading\"\n [skeletonTheme]=\"skeletonTheme\"\n ></osl-dynamic-form>\n </div>\n\n <!-- Footer -->\n <div class=\"orf-footer\">\n <button\n class=\"orf-generate-btn\"\n (click)=\"generate()\"\n [disabled]=\"generating\"\n type=\"button\"\n >\n @if (generating) {\n <span class=\"orf-generate-btn__spinner\"></span>\n <span>Generating...</span>\n } @else {\n <mat-icon>{{ typeIcon(selectedType) }}</mat-icon>\n <span>Generate {{ typeLabel(selectedType) }}</span>\n <mat-icon class=\"orf-generate-btn__arrow\">arrow_forward</mat-icon>\n }\n </button>\n </div>\n\n </div>\n}\n\n<!-- \u2500\u2500 Full-screen results 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 -->\n@if (showResultView) {\n <!-- <div class=\"orf-results-view\"> -->\n\n <!-- Results header bar -->\n <div class=\"orf-results-header\">\n\n <div class=\"orf-results-header__left\">\n <button class=\"orf-back-btn\" (click)=\"backToForm()\" type=\"button\">\n <mat-icon>arrow_back</mat-icon>\n <span>Back</span>\n </button>\n <div class=\"orf-results-header__sep\"></div>\n <mat-icon class=\"orf-results-header__report-icon\">assessment</mat-icon>\n <span class=\"orf-results-header__title\">{{ title }}</span>\n <span class=\"orf-results-badge\">\n <mat-icon>table_rows</mat-icon>\n {{ resultDatasource.length | number }} records\n </span>\n </div>\n\n <div class=\"orf-results-header__right\">\n @if (generateOptions.includes('excel')) {\n <button class=\"orf-reexport-btn orf-reexport-btn--excel\" (click)=\"reExportExcel()\" type=\"button\">\n <mat-icon>table_chart</mat-icon>\n <span>Excel</span>\n </button>\n }\n @if (generateOptions.includes('pdf')) {\n <button class=\"orf-reexport-btn orf-reexport-btn--pdf\" (click)=\"reExportPdf()\" type=\"button\">\n <mat-icon>picture_as_pdf</mat-icon>\n <span>PDF</span>\n </button>\n }\n </div>\n\n </div>\n\n <!-- Report grid -->\n <div class=\"orf-results-body\">\n <osl-report-grid\n [columns]=\"reportColumns\"\n [datasource]=\"resultDatasource\"\n [loading]=\"generating\"\n [title]=\"title\"\n tableHeight=\"calc(100vh - 200px)\"\n [pageSize]=\"pageSize\"\n [exportable]=\"true\"\n [showAggregates]=\"true\"\n [striped]=\"true\"\n ></osl-report-grid>\n </div>\n\n <!-- </div> -->\n}\n", styles: ["@keyframes orf-fade-in{0%{opacity:0;transform:translateY(12px)}to{opacity:1;transform:translateY(0)}}@keyframes orf-slide-in{0%{opacity:0;transform:translate(-10px)}to{opacity:1;transform:translate(0)}}@keyframes orf-spin{to{transform:rotate(360deg)}}.orf-card{background:#fff;border-radius:12px;border:1px solid #e5e7eb;box-shadow:0 4px 20px #00000014,0 1px 4px #0000000a;overflow:hidden}.orf-header{background:linear-gradient(135deg,#1e3a8a,#2563eb 55%,#60a5fa);padding:22px 24px;display:flex;align-items:center;justify-content:space-between;gap:16px;position:relative;overflow:hidden;flex-wrap:wrap}.orf-header:before{content:\"\";position:absolute;width:260px;height:260px;border-radius:50%;background:#ffffff0d;top:-110px;right:10px;pointer-events:none}.orf-header:after{content:\"\";position:absolute;width:160px;height:160px;border-radius:50%;background:#ffffff12;bottom:-70px;right:200px;pointer-events:none}.orf-header__left{display:flex;align-items:center;gap:14px;position:relative;z-index:1}.orf-header__icon-wrap{width:50px;height:50px;border-radius:14px;background:#ffffff26;border:1px solid rgba(255,255,255,.22);display:flex;align-items:center;justify-content:center;flex-shrink:0;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px)}.orf-header__icon{color:#fff;font-size:26px;width:26px;height:26px}.orf-header__text{display:flex;flex-direction:column;gap:3px}.orf-header__title{margin:0;font-size:19px;font-weight:700;color:#fff;letter-spacing:-.015em;line-height:1.2}.orf-header__sub{margin:0;font-size:12px;color:#ffffffad}.orf-header__right{display:flex;align-items:center;gap:8px;position:relative;z-index:1;flex-wrap:wrap}.orf-type-btn{display:inline-flex;align-items:center;gap:6px;padding:8px 16px;border-radius:8px;border:1px solid rgba(255,255,255,.28);background:#ffffff1f;color:#ffffffd1;font-size:13px;font-weight:500;cursor:pointer;transition:all .2s ease;white-space:nowrap;-webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px);font-family:inherit}.orf-type-btn mat-icon{font-size:16px;width:16px;height:16px}.orf-type-btn:hover{background:#ffffff38;border-color:#ffffff73;color:#fff;transform:translateY(-1px)}.orf-type-btn.active{background:#fffffff2;border-color:transparent;color:#1e3a8a;box-shadow:0 4px 14px #00000038;transform:translateY(-1px);font-weight:600}.orf-type-btn.active mat-icon{color:#1e3a8a}.orf-type-btn--pdf.active{color:#dc2626}.orf-type-btn--pdf.active mat-icon{color:#dc2626}.orf-type-btn--excel.active{color:#16a34a}.orf-type-btn--excel.active mat-icon{color:#16a34a}.orf-type-btn--grid.active{color:#2563eb}.orf-type-btn--grid.active mat-icon{color:#2563eb}.orf-divider{height:1px;background:linear-gradient(90deg,#e5e7eb,#c7d2fe,#e5e7eb)}.orf-body{padding:20px 20px 4px}.orf-footer{padding:12px 20px 20px;display:flex;justify-content:flex-end}.orf-generate-btn{display:inline-flex;align-items:center;gap:8px;padding:10px 22px;border-radius:8px;border:none;background:linear-gradient(135deg,#1e3a8a,#2563eb);color:#fff;font-size:14px;font-weight:600;cursor:pointer;transition:all .25s ease;box-shadow:0 4px 16px #2563eb52;letter-spacing:.01em;font-family:inherit}.orf-generate-btn mat-icon{font-size:18px;width:18px;height:18px}.orf-generate-btn__arrow{transition:transform .2s ease;font-size:16px!important;width:16px!important;height:16px!important;margin-left:2px}.orf-generate-btn:hover:not([disabled]){background:linear-gradient(135deg,#1e40af,#1d4ed8);box-shadow:0 6px 22px #2563eb7a;transform:translateY(-1px)}.orf-generate-btn:hover:not([disabled]) .orf-generate-btn__arrow{transform:translate(4px)}.orf-generate-btn:active:not([disabled]){transform:translateY(0);box-shadow:0 2px 8px #2563eb38}.orf-generate-btn[disabled]{opacity:.72;cursor:not-allowed;box-shadow:none}.orf-generate-btn__spinner{width:16px;height:16px;border:2px solid rgba(255,255,255,.3);border-top-color:#fff;border-radius:50%;display:inline-block;animation:orf-spin .7s linear infinite;flex-shrink:0}.orf-results-view{position:fixed;inset:0;z-index:200;display:flex;flex-direction:column;background:#f1f5f9;animation:orf-fade-in .25s ease}.orf-results-header{flex-shrink:0;height:56px;background:linear-gradient(135deg,#1e3a8a,#2563eb 60%,#3b82f6);display:flex;align-items:center;justify-content:space-between;padding:0 16px;gap:12px;position:relative;overflow:hidden;box-shadow:0 2px 12px #2563eb66}.orf-results-header:after{content:\"\";position:absolute;width:180px;height:180px;border-radius:50%;background:#ffffff0f;top:-80px;right:60px;pointer-events:none}.orf-results-header__left{display:flex;align-items:center;gap:10px;position:relative;z-index:1;min-width:0;flex:1}.orf-results-header__sep{width:1px;height:22px;background:#ffffff40;flex-shrink:0}.orf-results-header__report-icon{color:#ffffffd9;font-size:20px;width:20px;height:20px;flex-shrink:0}.orf-results-header__title{font-size:15px;font-weight:700;color:#fff;letter-spacing:-.01em;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.orf-results-badge{display:inline-flex;align-items:center;gap:4px;padding:3px 10px;border-radius:20px;background:#ffffff26;border:1px solid rgba(255,255,255,.2);color:#ffffffe6;font-size:11px;font-weight:600;white-space:nowrap;flex-shrink:0;animation:orf-slide-in .3s ease}.orf-results-badge mat-icon{font-size:13px;width:13px;height:13px}.orf-back-btn{display:inline-flex;align-items:center;gap:5px;padding:6px 14px;border-radius:7px;border:1px solid rgba(255,255,255,.3);background:#ffffff24;color:#fff;font-size:13px;font-weight:500;cursor:pointer;transition:all .18s ease;-webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px);font-family:inherit;flex-shrink:0}.orf-back-btn mat-icon{font-size:17px;width:17px;height:17px;transition:transform .18s ease}.orf-back-btn:hover{background:#ffffff3d;border-color:#ffffff80}.orf-back-btn:hover mat-icon{transform:translate(-3px)}.orf-back-btn:active{transform:scale(.97)}.orf-results-header__right{display:flex;align-items:center;gap:8px;position:relative;z-index:1;flex-shrink:0}.orf-reexport-btn{display:inline-flex;align-items:center;gap:5px;padding:6px 13px;border-radius:7px;border:1px solid rgba(255,255,255,.28);background:#ffffff1f;color:#ffffffd9;font-size:12px;font-weight:500;cursor:pointer;transition:all .18s ease;-webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px);font-family:inherit;white-space:nowrap}.orf-reexport-btn mat-icon{font-size:15px;width:15px;height:15px}.orf-reexport-btn:hover{background:#ffffff38;border-color:#ffffff73;color:#fff;transform:translateY(-1px)}.orf-reexport-btn--excel:hover{background:#16a34a59;border-color:#16a34a80}.orf-reexport-btn--pdf:hover{background:#dc262659;border-color:#dc262680}.orf-results-body{flex:1;overflow:hidden;padding:12px 12px 0;display:flex;flex-direction:column}.orf-results-body osl-report-grid{flex:1;display:block}\n"] }]
3729
+ }], ctorParameters: () => [{ type: i0.ChangeDetectorRef }], propDecorators: { title: [{
3730
+ type: Input
3731
+ }], formElements: [{
3732
+ type: Input
3733
+ }], model: [{
3734
+ type: Input
3735
+ }], modelChange: [{
3736
+ type: Output
3737
+ }], generateOptions: [{
3738
+ type: Input
3739
+ }], reportColumns: [{
3740
+ type: Input
3741
+ }], onGenerate: [{
3742
+ type: Input
3743
+ }], skeletonLoading: [{
3744
+ type: Input
3745
+ }], skeletonTheme: [{
3746
+ type: Input
3747
+ }], pageSize: [{
3748
+ type: Input
3749
+ }] } });
3750
+
2928
3751
  class FormStructureModule {
2929
3752
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: FormStructureModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
2930
3753
  static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.2.9", ngImport: i0, type: FormStructureModule, declarations: [DynamicForm,
@@ -2942,9 +3765,14 @@ class FormStructureModule {
2942
3765
  OslSearchbar,
2943
3766
  OslGrid,
2944
3767
  OslFormGrid,
2945
- OslAutocompleteLister], imports: [NgTemplateOutlet,
3768
+ OslAutocompleteLister,
3769
+ OslReportGrid,
3770
+ OslReportForm], imports: [NgTemplateOutlet,
2946
3771
  NgStyle,
3772
+ NgClass,
2947
3773
  DatePipe,
3774
+ DecimalPipe,
3775
+ UpperCasePipe,
2948
3776
  FormsModule,
2949
3777
  ReactiveFormsModule,
2950
3778
  MatFormFieldModule,
@@ -2955,7 +3783,9 @@ class FormStructureModule {
2955
3783
  MatButtonModule,
2956
3784
  OslSkeletonModule,
2957
3785
  MatDatepickerModule,
2958
- MatMenuModule], exports: [DynamicForm, OslSetup, OslGrid, OslFormGrid, Oslinput,
3786
+ MatMenuModule,
3787
+ ScrollingModule,
3788
+ DragDropModule], exports: [DynamicForm, OslSetup, OslGrid, OslFormGrid, Oslinput,
2959
3789
  Osltextarea,
2960
3790
  OslSelect,
2961
3791
  OslRadio,
@@ -2966,7 +3796,7 @@ class FormStructureModule {
2966
3796
  OslCheckbox,
2967
3797
  OslButton,
2968
3798
  OslSetup,
2969
- OslSearchbar, OslAutocompleteLister] });
3799
+ OslSearchbar, OslAutocompleteLister, OslReportGrid, OslReportForm] });
2970
3800
  static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: FormStructureModule, providers: [
2971
3801
  { provide: AUTOCOMPLETE_LISTER_COMPONENT, useValue: OslAutocompleteLister },
2972
3802
  ], imports: [FormsModule,
@@ -2978,7 +3808,9 @@ class FormStructureModule {
2978
3808
  MatButtonModule,
2979
3809
  OslSkeletonModule,
2980
3810
  MatDatepickerModule,
2981
- MatMenuModule] });
3811
+ MatMenuModule,
3812
+ ScrollingModule,
3813
+ DragDropModule] });
2982
3814
  }
2983
3815
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: FormStructureModule, decorators: [{
2984
3816
  type: NgModule,
@@ -2999,12 +3831,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
2999
3831
  OslSearchbar,
3000
3832
  OslGrid,
3001
3833
  OslFormGrid,
3002
- OslAutocompleteLister
3834
+ OslAutocompleteLister,
3835
+ OslReportGrid,
3836
+ OslReportForm,
3003
3837
  ],
3004
3838
  imports: [
3005
3839
  NgTemplateOutlet,
3006
3840
  NgStyle,
3841
+ NgClass,
3007
3842
  DatePipe,
3843
+ DecimalPipe,
3844
+ UpperCasePipe,
3008
3845
  FormsModule,
3009
3846
  ReactiveFormsModule,
3010
3847
  MatFormFieldModule,
@@ -3015,7 +3852,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
3015
3852
  MatButtonModule,
3016
3853
  OslSkeletonModule,
3017
3854
  MatDatepickerModule,
3018
- MatMenuModule
3855
+ MatMenuModule,
3856
+ ScrollingModule,
3857
+ DragDropModule,
3019
3858
  ],
3020
3859
  exports: [DynamicForm, OslSetup, OslGrid, OslFormGrid, Oslinput,
3021
3860
  Osltextarea,
@@ -3028,7 +3867,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
3028
3867
  OslCheckbox,
3029
3868
  OslButton,
3030
3869
  OslSetup,
3031
- OslSearchbar, OslAutocompleteLister],
3870
+ OslSearchbar, OslAutocompleteLister, OslReportGrid, OslReportForm],
3032
3871
  providers: [
3033
3872
  { provide: AUTOCOMPLETE_LISTER_COMPONENT, useValue: OslAutocompleteLister },
3034
3873
  ],
@@ -4588,5 +5427,5 @@ var validation_util = /*#__PURE__*/Object.freeze({
4588
5427
  * Generated bundle index. Do not edit.
4589
5428
  */
4590
5429
 
4591
- export { array_util as ArrayUtil, date_util as DateUtil, DeleteConfirmation, DeleteConfirmationData, Dialog, DialogWrapper, DynamicForm, FormStructureModule, Httpbase, number_util as NumberUtil, object_util as ObjectUtil, OslAutocomplete, OslAutocompleteLister, OslBaseExtended, OslButton, OslCheckbox, OslDatepicker, OslFileUpload, OslFormGrid, OslGrid, OslRadio, OslSearchbar, OslSelect, OslSetup, OslSkeletonDirective, OslSkeletonModule, OslSkeletonThemeService, OslSlideToggle, Oslinput, Osltextarea, storage_util as StorageUtil, string_util as StringUtil, validation_util as ValidationUtil, baseComponent };
5430
+ export { array_util as ArrayUtil, date_util as DateUtil, DeleteConfirmation, DeleteConfirmationData, Dialog, DialogWrapper, DynamicForm, FormStructureModule, Httpbase, number_util as NumberUtil, object_util as ObjectUtil, OslAutocomplete, OslAutocompleteLister, OslBaseExtended, OslButton, OslCheckbox, OslDatepicker, OslFileUpload, OslFormGrid, OslGrid, OslRadio, OslReportForm, OslReportGrid, OslSearchbar, OslSelect, OslSetup, OslSkeletonDirective, OslSkeletonModule, OslSkeletonThemeService, OslSlideToggle, Oslinput, Osltextarea, storage_util as StorageUtil, string_util as StringUtil, validation_util as ValidationUtil, baseComponent };
4592
5431
  //# sourceMappingURL=osl-base-extended.mjs.map