osl-base-extended 10.0.0 → 10.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1189,6 +1189,8 @@ class Oslinput {
|
|
|
1189
1189
|
isCapitalize = false;
|
|
1190
1190
|
/** Enforce N decimal places. Shows 0.00 when blank; auto-pads on blur. */
|
|
1191
1191
|
decimalPortion = null;
|
|
1192
|
+
/** Allow a leading minus sign for 'int' type and decimal inputs. Default false. */
|
|
1193
|
+
allowNegative = true;
|
|
1192
1194
|
modelChange = new EventEmitter();
|
|
1193
1195
|
changeEv = new EventEmitter();
|
|
1194
1196
|
showPassword = false;
|
|
@@ -1241,13 +1243,16 @@ class Oslinput {
|
|
|
1241
1243
|
}
|
|
1242
1244
|
}
|
|
1243
1245
|
formatDecimal(value) {
|
|
1246
|
+
const isNegative = this.allowNegative && value.trim().startsWith('-');
|
|
1244
1247
|
const cleaned = value.replace(/[^\d.]/g, '');
|
|
1245
1248
|
const num = parseFloat(cleaned);
|
|
1246
1249
|
if (isNaN(num))
|
|
1247
1250
|
return (0).toFixed(this.decimalPortion);
|
|
1248
|
-
|
|
1251
|
+
const formatted = num.toFixed(this.decimalPortion);
|
|
1252
|
+
return isNegative && num !== 0 ? '-' + formatted : formatted;
|
|
1249
1253
|
}
|
|
1250
1254
|
cleanDecimalInput(value) {
|
|
1255
|
+
const isNegative = this.allowNegative && value.trim().startsWith('-');
|
|
1251
1256
|
let cleaned = value.replace(/[^\d.]/g, '');
|
|
1252
1257
|
const parts = cleaned.split('.');
|
|
1253
1258
|
if (parts.length > 2) {
|
|
@@ -1256,7 +1261,7 @@ class Oslinput {
|
|
|
1256
1261
|
if (parts.length >= 2 && parts[1].length > this.decimalPortion) {
|
|
1257
1262
|
cleaned = parts[0] + '.' + parts[1].substring(0, this.decimalPortion);
|
|
1258
1263
|
}
|
|
1259
|
-
return cleaned;
|
|
1264
|
+
return (isNegative ? '-' : '') + cleaned;
|
|
1260
1265
|
}
|
|
1261
1266
|
onKeyDown(event) {
|
|
1262
1267
|
if (event.ctrlKey || event.metaKey || event.altKey)
|
|
@@ -1268,6 +1273,12 @@ class Oslinput {
|
|
|
1268
1273
|
return;
|
|
1269
1274
|
}
|
|
1270
1275
|
if (this.type === 'int') {
|
|
1276
|
+
if (event.key === '-') {
|
|
1277
|
+
if (!this.allowNegativeAt(event.target)) {
|
|
1278
|
+
event.preventDefault();
|
|
1279
|
+
}
|
|
1280
|
+
return;
|
|
1281
|
+
}
|
|
1271
1282
|
if (!/\d/.test(event.key)) {
|
|
1272
1283
|
event.preventDefault();
|
|
1273
1284
|
}
|
|
@@ -1275,6 +1286,12 @@ class Oslinput {
|
|
|
1275
1286
|
}
|
|
1276
1287
|
if (this.decimalPortion !== null) {
|
|
1277
1288
|
const key = event.key;
|
|
1289
|
+
if (key === '-') {
|
|
1290
|
+
if (!this.allowNegativeAt(event.target)) {
|
|
1291
|
+
event.preventDefault();
|
|
1292
|
+
}
|
|
1293
|
+
return;
|
|
1294
|
+
}
|
|
1278
1295
|
if (!/[\d.]/.test(key)) {
|
|
1279
1296
|
event.preventDefault();
|
|
1280
1297
|
return;
|
|
@@ -1310,6 +1327,11 @@ class Oslinput {
|
|
|
1310
1327
|
event.preventDefault();
|
|
1311
1328
|
}
|
|
1312
1329
|
}
|
|
1330
|
+
allowNegativeAt(input) {
|
|
1331
|
+
if (!this.allowNegative)
|
|
1332
|
+
return false;
|
|
1333
|
+
return (input.selectionStart ?? 0) === 0 && !input.value.includes('-');
|
|
1334
|
+
}
|
|
1313
1335
|
isKeyAllowedByMask(key) {
|
|
1314
1336
|
const maskSlots = new Set();
|
|
1315
1337
|
for (const c of this.mask) {
|
|
@@ -1338,8 +1360,9 @@ class Oslinput {
|
|
|
1338
1360
|
if (this.type === 'int') {
|
|
1339
1361
|
const dotIndex = value.indexOf('.');
|
|
1340
1362
|
const truncated = dotIndex !== -1 ? value.substring(0, dotIndex) : value;
|
|
1363
|
+
const isNegative = this.allowNegative && truncated.trim().startsWith('-');
|
|
1341
1364
|
const digits = truncated.replace(/[^\d]/g, '');
|
|
1342
|
-
this.model = digits === '' ? null : parseInt(digits, 10);
|
|
1365
|
+
this.model = digits === '' ? (isNegative ? '-' : null) : parseInt((isNegative ? '-' : '') + digits, 10);
|
|
1343
1366
|
this.modelChange.emit(this.model);
|
|
1344
1367
|
return;
|
|
1345
1368
|
}
|
|
@@ -1390,7 +1413,7 @@ class Oslinput {
|
|
|
1390
1413
|
return result;
|
|
1391
1414
|
}
|
|
1392
1415
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: Oslinput, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1393
|
-
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", suffixRef: "suffixRef", 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 class=\"label-text\" [oslSkeleton]=\"skeletonLoading\" [oslSkeletonTheme]=\"skeletonTheme\" [title]=\"label\">{{label}}</span><span class=\"txt-clr-red label-required\">{{required?'*':''}}</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(suffixRef){\r\n <ng-container *ngTemplateOutlet=\"suffixRef; context: { $implicit: this }\"></ng-container>\r\n \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;display:flex;align-items:center;overflow:hidden}.label-text{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}.label-required{flex-shrink:0}.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}.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{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.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i1$2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1$2.EmailValidator, selector: "[email][formControlName],[email][formControl],[email][ngModel]", inputs: ["email"] }, { kind: "directive", type: i1$2.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: i3.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"] }] });
|
|
1416
|
+
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", suffixRef: "suffixRef", skeletonLoading: "skeletonLoading", skeletonTheme: "skeletonTheme", onlyChars: "onlyChars", isCapitalize: "isCapitalize", decimalPortion: "decimalPortion", allowNegative: "allowNegative" }, 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 class=\"label-text\" [oslSkeleton]=\"skeletonLoading\" [oslSkeletonTheme]=\"skeletonTheme\" [title]=\"label\">{{label}}</span><span class=\"txt-clr-red label-required\">{{required?'*':''}}</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(suffixRef){\r\n <ng-container *ngTemplateOutlet=\"suffixRef; context: { $implicit: this }\"></ng-container>\r\n \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;display:flex;align-items:center;overflow:hidden}.label-text{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}.label-required{flex-shrink:0}.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}.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{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.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i1$2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1$2.EmailValidator, selector: "[email][formControlName],[email][formControl],[email][ngModel]", inputs: ["email"] }, { kind: "directive", type: i1$2.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: i3.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"] }] });
|
|
1394
1417
|
}
|
|
1395
1418
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: Oslinput, decorators: [{
|
|
1396
1419
|
type: Component,
|
|
@@ -1452,6 +1475,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
1452
1475
|
}], decimalPortion: [{
|
|
1453
1476
|
type: Input,
|
|
1454
1477
|
args: ['decimalPortion']
|
|
1478
|
+
}], allowNegative: [{
|
|
1479
|
+
type: Input,
|
|
1480
|
+
args: ['allowNegative']
|
|
1455
1481
|
}], modelChange: [{
|
|
1456
1482
|
type: Output
|
|
1457
1483
|
}], changeEv: [{
|
|
@@ -3069,11 +3095,11 @@ class DynamicForm {
|
|
|
3069
3095
|
return list.flatMap(e => e.rows?.length ? [e, ...this._flatElements(e.rows)] : [e]);
|
|
3070
3096
|
}
|
|
3071
3097
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: DynamicForm, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
|
|
3072
|
-
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) {\n <div class=\"row align-items-center w-100\">\n @for(elem of elements; track elem) {\n @if(!(elem.hideIf ? elem.hideIf(model) : elem.hide)) {\n <ng-container *ngTemplateOutlet=\"formField; context: { $implicit: elem }\"></ng-container>\n }\n }\n </div>\n}\n\n<ng-template #formField let-elem>\n @if(elem.hideIf ? !elem.hideIf(model) : true) {\n @switch (elem.elementType) {\n\n @case (\"textbox\") {\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\" [attr.data-field-key]=\"elem.key\">\n <osl-input\n [skeletonLoading]=\"skeletonLoading\"\n [skeletonTheme]=\"skeletonTheme\"\n [label]=\"elem.label\"\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\n [type]=\"elem.inputType || 'text'\"\n [placeholder]=\"elem.placeholder || ''\"\n [mask]=\"elem.mask || ''\"\n [min]=\"elem.min ?? ''\"\n [max]=\"elem.max ?? ''\"\n [minLength]=\"elem.minLength ?? null\"\n [maxLength]=\"elem.maxLength ?? null\"\n [prefixIcon]=\"elem.prefixIcon || ''\"\n [suffixIcon]=\"elem.suffixIcon || ''\"\n [suffixRef]=\"elem.suffixRef || null\"\n [onlyChars]=\"!!elem.onlyChars\"\n [decimalPortion]=\"elem.decimalPortion ?? null\"\n (changeEv)=\"elem.change ? elem.change(model) : null\"\n [model]=\"model[elem.key]\"\n (modelChange)=\"onModelChange($event, elem.key)\"\n [isCapitalize]=\"elem.isCapitalize\"\n ></osl-input>\n </div>\n }\n\n @case (\"textarea\") {\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\" [attr.data-field-key]=\"elem.key\">\n <osl-textarea\n [label]=\"elem.label\"\n [skeletonLoading]=\"skeletonLoading\"\n [skeletonTheme]=\"skeletonTheme\"\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\n [rows]=\"elem.textareaRows || 3\"\n [placeholder]=\"elem.placeholder || ''\"\n [maxLength]=\"elem.maxLength ?? null\"\n [minLength]=\"elem.minLength ?? null\"\n [characterCounter]=\"!!elem.characterCounter\"\n [resize]=\"elem.resize || 'none'\"\n (changeEv)=\"elem.change ? elem.change(model) : null\"\n [model]=\"model[elem.key]\"\n (modelChange)=\"onModelChange($event, elem.key)\"\n ></osl-textarea>\n </div>\n }\n\n @case (\"select\") {\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\" [attr.data-field-key]=\"elem.key\">\n <osl-select\n [label]=\"elem.label\"\n [skeletonLoading]=\"skeletonLoading\"\n [skeletonTheme]=\"skeletonTheme\"\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\n [datasource]=\"elem.datasource || []\"\n [displayField]=\"elem.displayField || ''\"\n [valueField]=\"elem.valueField || ''\"\n [placeholder]=\"elem.selectPlaceholder || 'Select...'\"\n [loading]=\"elem.loadingIf ? elem.loadingIf(model) : false\"\n [clearable]=\"!!elem.clearable\"\n (changeEv)=\"elem.change ? onSelectChange(elem, $event) : null\"\n [model]=\"model[elem.key]\"\n (modelChange)=\"onModelChange($event, elem.key)\"\n ></osl-select>\n </div>\n }\n\n @case (\"radio\") {\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\" [attr.data-field-key]=\"elem.key\">\n <osl-radio\n [label]=\"elem.label\"\n [skeletonLoading]=\"skeletonLoading\"\n [skeletonTheme]=\"skeletonTheme\"\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\n [datasource]=\"elem.datasource || []\"\n [displayField]=\"elem.displayField || ''\"\n [valueField]=\"elem.valueField || ''\"\n [inline]=\"!!elem.inline\"\n (changeEv)=\"elem.change ? elem.change(model) : null\"\n [model]=\"model[elem.key]\"\n (modelChange)=\"onModelChange($event, elem.key)\"\n ></osl-radio>\n </div>\n }\n\n @case (\"slide-toggle\") {\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\" [attr.data-field-key]=\"elem.key\">\n <osl-slide-toggle\n [label]=\"elem.label\"\n [skeletonLoading]=\"skeletonLoading\"\n [skeletonTheme]=\"skeletonTheme\"\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\n [labelPosition]=\"elem.labelPosition || 'after'\"\n [trueLabel]=\"elem.trueLabel || ''\"\n [falseLabel]=\"elem.falseLabel || ''\"\n (changeEv)=\"elem.change ? elem.change(model) : null\"\n [model]=\"model[elem.key]\"\n (modelChange)=\"onModelChange($event, elem.key)\"\n ></osl-slide-toggle>\n </div>\n }\n\n @case (\"autocomplete\") {\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\" [attr.data-field-key]=\"elem.key\">\n <osl-autocomplete\n [label]=\"elem.label\"\n [skeletonLoading]=\"skeletonLoading\"\n [skeletonTheme]=\"skeletonTheme\"\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\n [(datasource)]=\"elem.datasource\"\n [displayField]=\"elem.displayField || ''\"\n [valueField]=\"elem.valueField || ''\"\n [placeholder]=\"elem.autocompletePlaceholder || 'Type to search...'\"\n [loading]=\"elem.loadingIf ? elem.loadingIf(model) : false\"\n (changeEv)=\"onFieldChange(elem, $event)\"\n [(model)]=\"model[elem.key]\"\n [methodName]=\"elem.apiMethod\"\n [service]=\"elem.apiService\"\n [object]=\"model[elem.objectName]\"\n [searchType]=\"elem.searchType\"\n [configMethodName]=\"elem.apiConfigMethod\"\n [isLister]=\"elem.isListerAutocomplete\"\n [apiBody]=\"elem.apiBody ? elem.apiBody(model):null\"\n [displayFn]=\"elem.displayFn || null\"\n ></osl-autocomplete>\n </div>\n }\n\n @case (\"file-uploader\") {\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\" [attr.data-field-key]=\"elem.key\">\n <osl-file-upload\n [skeletonLoading]=\"skeletonLoading\"\n [skeletonTheme]=\"skeletonTheme\"\n [label]=\"elem.label\"\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\n [accept]=\"elem.accept || ''\"\n [multiple]=\"!!elem.multiple\"\n [maxSize]=\"elem.maxFileSize || 0\"\n [fileMode]=\"elem.fileMode || 'raw'\"\n [downloadFn]=\"elem.fileDownloadFn ? elem.fileDownloadFn.bind(null, model) : null\"\n (changeEv)=\"elem.change ? elem.change(model) : null\"\n [model]=\"model[elem.key]\"\n (modelChange)=\"onModelChange($event, elem.key)\"\n ></osl-file-upload>\n </div>\n }\n\n @case (\"datepicker\") {\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\" [attr.data-field-key]=\"elem.key\">\n <osl-datepicker\n [skeletonLoading]=\"skeletonLoading\"\n [skeletonTheme]=\"skeletonTheme\"\n [label]=\"elem.label\"\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\n [dateType]=\"elem.dateType || 'date'\"\n [placeholder]=\"elem.placeholder || ''\"\n [minDate]=\"elem.minDateIf ? elem.minDateIf(model) : elem.minDate || ''\"\n [maxDate]=\"elem.maxDateIf ? elem.maxDateIf(model) : elem.maxDate || ''\"\n (changeEv)=\"elem.change ? elem.change(model) : null\"\n [model]=\"model[elem.key]\"\n (modelChange)=\"onModelChange($event, elem.key)\"\n ></osl-datepicker>\n </div>\n }\n\n @case (\"datetimepicker\") {\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\" [attr.data-field-key]=\"elem.key\">\n <osl-datetimepicker\n [skeletonLoading]=\"skeletonLoading\"\n [skeletonTheme]=\"skeletonTheme\"\n [label]=\"elem.label\"\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\n [placeholder]=\"elem.placeholder || ''\"\n [minDatetime]=\"elem.minDatetimeIf ? elem.minDatetimeIf(model) : elem.minDatetime || ''\"\n [maxDatetime]=\"elem.maxDatetimeIf ? elem.maxDatetimeIf(model) : elem.maxDatetime || ''\"\n [format]=\"elem.datetimepickerFormat || 'YYYY-MM-DDTHH:mm'\"\n [showSeconds]=\"!!elem.datetimepickerShowSeconds\"\n [enableMeridian]=\"!!elem.datetimepickerEnableMeridian\"\n (changeEv)=\"elem.change ? elem.change(model) : null\"\n [model]=\"model[elem.key]\"\n (modelChange)=\"onModelChange($event, elem.key)\"\n ></osl-datetimepicker>\n </div>\n }\n\n @case (\"checkbox\") {\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\" [attr.data-field-key]=\"elem.key\">\n <osl-checkbox\n [skeletonLoading]=\"skeletonLoading\"\n [skeletonTheme]=\"skeletonTheme\"\n [label]=\"elem.label\"\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\n [indeterminate]=\"!!elem.indeterminate\"\n (changeEv)=\"elem.change ? elem.change(model) : null\"\n [model]=\"model[elem.key]\"\n (modelChange)=\"onModelChange($event, elem.key)\"\n ></osl-checkbox>\n </div>\n }\n\n @case (\"button\") {\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\" [attr.data-field-key]=\"elem.key\">\n <osl-button\n [label]=\"elem.label\"\n [loading]=\"elem.loadingIf ? elem.loadingIf(model) : false\"\n variant=\"secondary\"\n (clickEv)=\"elem.change ? elem.change(model) : null\"\n ></osl-button>\n </div>\n }\n\n @case (\"fieldset\") {\n <div [class]=\"'col-md-'+elem.columns+' mt-3'\">\n <fieldset class=\"osl-fieldset\">\n @if(elem.label) {\n <legend class=\"osl-fieldset-legend\" [oslSkeleton]=\"skeletonLoading\" [oslSkeletonTheme]=\"skeletonTheme\">\n <span class=\"osl-fieldset-legend__text\">{{ elem.label }}</span>\n </legend>\n }\n <div class=\"row w-100 osl-fieldset-body\">\n @for(innerElem of elem.rows; track innerElem) {\n @if(!(innerElem.hideIf ? innerElem.hideIf(model) : innerElem.hide)) {\n <ng-container *ngTemplateOutlet=\"formField; context: { $implicit: innerElem }\"></ng-container>\n }\n }\n </div>\n </fieldset>\n </div>\n }\n\n @case (\"templateRef\"){\n <div [class]=\"'col-md-'+elem.columns+' mt-3'\">\n <ng-container *ngTemplateOutlet=\"elem.templateRef; context: { $implicit: elem}\"></ng-container>\n </div>\n }\n\n @case (\"spacer\") {\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\" [attr.data-field-key]=\"elem.key\">\n </div>\n }\n\n }\n }\n</ng-template>\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$1.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", "suffixRef", "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", "apiBody", "displayFn"], outputs: ["datasourceChange", "modelChange", "changeEv"] }, { kind: "component", type: OslFileUpload, selector: "osl-file-upload", inputs: ["label", "required", "disabled", "model", "accept", "multiple", "maxSize", "skeletonLoading", "skeletonTheme", "fileMode", "downloadFn"], 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: OslDatetimepicker, selector: "osl-datetimepicker", inputs: ["label", "required", "disabled", "model", "placeholder", "minDatetime", "maxDatetime", "format", "displayFormat", "showSeconds", "enableMeridian", "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"] }] });
|
|
3098
|
+
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) {\n <div class=\"row align-items-center w-100\">\n @for(elem of elements; track elem) {\n @if(!(elem.hideIf ? elem.hideIf(model) : elem.hide)) {\n <ng-container *ngTemplateOutlet=\"formField; context: { $implicit: elem }\"></ng-container>\n }\n }\n </div>\n}\n\n<ng-template #formField let-elem>\n @if(elem.hideIf ? !elem.hideIf(model) : true) {\n @switch (elem.elementType) {\n\n @case (\"textbox\") {\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\" [attr.data-field-key]=\"elem.key\">\n <osl-input\n [skeletonLoading]=\"skeletonLoading\"\n [skeletonTheme]=\"skeletonTheme\"\n [label]=\"elem.label\"\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\n [type]=\"elem.inputType || 'text'\"\n [placeholder]=\"elem.placeholder || ''\"\n [mask]=\"elem.mask || ''\"\n [min]=\"elem.min ?? ''\"\n [max]=\"elem.max ?? ''\"\n [minLength]=\"elem.minLength ?? null\"\n [maxLength]=\"elem.maxLength ?? null\"\n [prefixIcon]=\"elem.prefixIcon || ''\"\n [suffixIcon]=\"elem.suffixIcon || ''\"\n [suffixRef]=\"elem.suffixRef || null\"\n [onlyChars]=\"!!elem.onlyChars\"\n [decimalPortion]=\"elem.decimalPortion ?? null\"\n [allowNegative]=\"!!elem.allowNegative\"\n (changeEv)=\"elem.change ? elem.change(model) : null\"\n [model]=\"model[elem.key]\"\n (modelChange)=\"onModelChange($event, elem.key)\"\n [isCapitalize]=\"elem.isCapitalize\"\n ></osl-input>\n </div>\n }\n\n @case (\"textarea\") {\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\" [attr.data-field-key]=\"elem.key\">\n <osl-textarea\n [label]=\"elem.label\"\n [skeletonLoading]=\"skeletonLoading\"\n [skeletonTheme]=\"skeletonTheme\"\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\n [rows]=\"elem.textareaRows || 3\"\n [placeholder]=\"elem.placeholder || ''\"\n [maxLength]=\"elem.maxLength ?? null\"\n [minLength]=\"elem.minLength ?? null\"\n [characterCounter]=\"!!elem.characterCounter\"\n [resize]=\"elem.resize || 'none'\"\n (changeEv)=\"elem.change ? elem.change(model) : null\"\n [model]=\"model[elem.key]\"\n (modelChange)=\"onModelChange($event, elem.key)\"\n ></osl-textarea>\n </div>\n }\n\n @case (\"select\") {\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\" [attr.data-field-key]=\"elem.key\">\n <osl-select\n [label]=\"elem.label\"\n [skeletonLoading]=\"skeletonLoading\"\n [skeletonTheme]=\"skeletonTheme\"\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\n [datasource]=\"elem.datasource || []\"\n [displayField]=\"elem.displayField || ''\"\n [valueField]=\"elem.valueField || ''\"\n [placeholder]=\"elem.selectPlaceholder || 'Select...'\"\n [loading]=\"elem.loadingIf ? elem.loadingIf(model) : false\"\n [clearable]=\"!!elem.clearable\"\n (changeEv)=\"elem.change ? onSelectChange(elem, $event) : null\"\n [model]=\"model[elem.key]\"\n (modelChange)=\"onModelChange($event, elem.key)\"\n ></osl-select>\n </div>\n }\n\n @case (\"radio\") {\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\" [attr.data-field-key]=\"elem.key\">\n <osl-radio\n [label]=\"elem.label\"\n [skeletonLoading]=\"skeletonLoading\"\n [skeletonTheme]=\"skeletonTheme\"\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\n [datasource]=\"elem.datasource || []\"\n [displayField]=\"elem.displayField || ''\"\n [valueField]=\"elem.valueField || ''\"\n [inline]=\"!!elem.inline\"\n (changeEv)=\"elem.change ? elem.change(model) : null\"\n [model]=\"model[elem.key]\"\n (modelChange)=\"onModelChange($event, elem.key)\"\n ></osl-radio>\n </div>\n }\n\n @case (\"slide-toggle\") {\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\" [attr.data-field-key]=\"elem.key\">\n <osl-slide-toggle\n [label]=\"elem.label\"\n [skeletonLoading]=\"skeletonLoading\"\n [skeletonTheme]=\"skeletonTheme\"\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\n [labelPosition]=\"elem.labelPosition || 'after'\"\n [trueLabel]=\"elem.trueLabel || ''\"\n [falseLabel]=\"elem.falseLabel || ''\"\n (changeEv)=\"elem.change ? elem.change(model) : null\"\n [model]=\"model[elem.key]\"\n (modelChange)=\"onModelChange($event, elem.key)\"\n ></osl-slide-toggle>\n </div>\n }\n\n @case (\"autocomplete\") {\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\" [attr.data-field-key]=\"elem.key\">\n <osl-autocomplete\n [label]=\"elem.label\"\n [skeletonLoading]=\"skeletonLoading\"\n [skeletonTheme]=\"skeletonTheme\"\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\n [(datasource)]=\"elem.datasource\"\n [displayField]=\"elem.displayField || ''\"\n [valueField]=\"elem.valueField || ''\"\n [placeholder]=\"elem.autocompletePlaceholder || 'Type to search...'\"\n [loading]=\"elem.loadingIf ? elem.loadingIf(model) : false\"\n (changeEv)=\"onFieldChange(elem, $event)\"\n [(model)]=\"model[elem.key]\"\n [methodName]=\"elem.apiMethod\"\n [service]=\"elem.apiService\"\n [object]=\"model[elem.objectName]\"\n [searchType]=\"elem.searchType\"\n [configMethodName]=\"elem.apiConfigMethod\"\n [isLister]=\"elem.isListerAutocomplete\"\n [apiBody]=\"elem.apiBody ? elem.apiBody(model):null\"\n [displayFn]=\"elem.displayFn || null\"\n ></osl-autocomplete>\n </div>\n }\n\n @case (\"file-uploader\") {\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\" [attr.data-field-key]=\"elem.key\">\n <osl-file-upload\n [skeletonLoading]=\"skeletonLoading\"\n [skeletonTheme]=\"skeletonTheme\"\n [label]=\"elem.label\"\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\n [accept]=\"elem.accept || ''\"\n [multiple]=\"!!elem.multiple\"\n [maxSize]=\"elem.maxFileSize || 0\"\n [fileMode]=\"elem.fileMode || 'raw'\"\n [downloadFn]=\"elem.fileDownloadFn ? elem.fileDownloadFn.bind(null, model) : null\"\n (changeEv)=\"elem.change ? elem.change(model) : null\"\n [model]=\"model[elem.key]\"\n (modelChange)=\"onModelChange($event, elem.key)\"\n ></osl-file-upload>\n </div>\n }\n\n @case (\"datepicker\") {\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\" [attr.data-field-key]=\"elem.key\">\n <osl-datepicker\n [skeletonLoading]=\"skeletonLoading\"\n [skeletonTheme]=\"skeletonTheme\"\n [label]=\"elem.label\"\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\n [dateType]=\"elem.dateType || 'date'\"\n [placeholder]=\"elem.placeholder || ''\"\n [minDate]=\"elem.minDateIf ? elem.minDateIf(model) : elem.minDate || ''\"\n [maxDate]=\"elem.maxDateIf ? elem.maxDateIf(model) : elem.maxDate || ''\"\n (changeEv)=\"elem.change ? elem.change(model) : null\"\n [model]=\"model[elem.key]\"\n (modelChange)=\"onModelChange($event, elem.key)\"\n ></osl-datepicker>\n </div>\n }\n\n @case (\"datetimepicker\") {\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\" [attr.data-field-key]=\"elem.key\">\n <osl-datetimepicker\n [skeletonLoading]=\"skeletonLoading\"\n [skeletonTheme]=\"skeletonTheme\"\n [label]=\"elem.label\"\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\n [placeholder]=\"elem.placeholder || ''\"\n [minDatetime]=\"elem.minDatetimeIf ? elem.minDatetimeIf(model) : elem.minDatetime || ''\"\n [maxDatetime]=\"elem.maxDatetimeIf ? elem.maxDatetimeIf(model) : elem.maxDatetime || ''\"\n [format]=\"elem.datetimepickerFormat || 'YYYY-MM-DDTHH:mm'\"\n [showSeconds]=\"!!elem.datetimepickerShowSeconds\"\n [enableMeridian]=\"!!elem.datetimepickerEnableMeridian\"\n (changeEv)=\"elem.change ? elem.change(model) : null\"\n [model]=\"model[elem.key]\"\n (modelChange)=\"onModelChange($event, elem.key)\"\n ></osl-datetimepicker>\n </div>\n }\n\n @case (\"checkbox\") {\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\" [attr.data-field-key]=\"elem.key\">\n <osl-checkbox\n [skeletonLoading]=\"skeletonLoading\"\n [skeletonTheme]=\"skeletonTheme\"\n [label]=\"elem.label\"\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\n [indeterminate]=\"!!elem.indeterminate\"\n (changeEv)=\"elem.change ? elem.change(model) : null\"\n [model]=\"model[elem.key]\"\n (modelChange)=\"onModelChange($event, elem.key)\"\n ></osl-checkbox>\n </div>\n }\n\n @case (\"button\") {\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\" [attr.data-field-key]=\"elem.key\">\n <osl-button\n [label]=\"elem.label\"\n [loading]=\"elem.loadingIf ? elem.loadingIf(model) : false\"\n variant=\"secondary\"\n (clickEv)=\"elem.change ? elem.change(model) : null\"\n ></osl-button>\n </div>\n }\n\n @case (\"fieldset\") {\n <div [class]=\"'col-md-'+elem.columns+' mt-3'\">\n <fieldset class=\"osl-fieldset\">\n @if(elem.label) {\n <legend class=\"osl-fieldset-legend\" [oslSkeleton]=\"skeletonLoading\" [oslSkeletonTheme]=\"skeletonTheme\">\n <span class=\"osl-fieldset-legend__text\">{{ elem.label }}</span>\n </legend>\n }\n <div class=\"row w-100 osl-fieldset-body\">\n @for(innerElem of elem.rows; track innerElem) {\n @if(!(innerElem.hideIf ? innerElem.hideIf(model) : innerElem.hide)) {\n <ng-container *ngTemplateOutlet=\"formField; context: { $implicit: innerElem }\"></ng-container>\n }\n }\n </div>\n </fieldset>\n </div>\n }\n\n @case (\"templateRef\"){\n <div [class]=\"'col-md-'+elem.columns+' mt-3'\">\n <ng-container *ngTemplateOutlet=\"elem.templateRef; context: { $implicit: elem}\"></ng-container>\n </div>\n }\n\n @case (\"spacer\") {\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\" [attr.data-field-key]=\"elem.key\">\n </div>\n }\n\n }\n }\n</ng-template>\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$1.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", "suffixRef", "skeletonLoading", "skeletonTheme", "onlyChars", "isCapitalize", "decimalPortion", "allowNegative"], 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", "apiBody", "displayFn"], outputs: ["datasourceChange", "modelChange", "changeEv"] }, { kind: "component", type: OslFileUpload, selector: "osl-file-upload", inputs: ["label", "required", "disabled", "model", "accept", "multiple", "maxSize", "skeletonLoading", "skeletonTheme", "fileMode", "downloadFn"], 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: OslDatetimepicker, selector: "osl-datetimepicker", inputs: ["label", "required", "disabled", "model", "placeholder", "minDatetime", "maxDatetime", "format", "displayFormat", "showSeconds", "enableMeridian", "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"] }] });
|
|
3073
3099
|
}
|
|
3074
3100
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: DynamicForm, decorators: [{
|
|
3075
3101
|
type: Component,
|
|
3076
|
-
args: [{ selector: 'osl-dynamic-form', standalone: false, template: "@if(elements && elements.length > 0) {\n <div class=\"row align-items-center w-100\">\n @for(elem of elements; track elem) {\n @if(!(elem.hideIf ? elem.hideIf(model) : elem.hide)) {\n <ng-container *ngTemplateOutlet=\"formField; context: { $implicit: elem }\"></ng-container>\n }\n }\n </div>\n}\n\n<ng-template #formField let-elem>\n @if(elem.hideIf ? !elem.hideIf(model) : true) {\n @switch (elem.elementType) {\n\n @case (\"textbox\") {\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\" [attr.data-field-key]=\"elem.key\">\n <osl-input\n [skeletonLoading]=\"skeletonLoading\"\n [skeletonTheme]=\"skeletonTheme\"\n [label]=\"elem.label\"\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\n [type]=\"elem.inputType || 'text'\"\n [placeholder]=\"elem.placeholder || ''\"\n [mask]=\"elem.mask || ''\"\n [min]=\"elem.min ?? ''\"\n [max]=\"elem.max ?? ''\"\n [minLength]=\"elem.minLength ?? null\"\n [maxLength]=\"elem.maxLength ?? null\"\n [prefixIcon]=\"elem.prefixIcon || ''\"\n [suffixIcon]=\"elem.suffixIcon || ''\"\n [suffixRef]=\"elem.suffixRef || null\"\n [onlyChars]=\"!!elem.onlyChars\"\n [decimalPortion]=\"elem.decimalPortion ?? null\"\n (changeEv)=\"elem.change ? elem.change(model) : null\"\n [model]=\"model[elem.key]\"\n (modelChange)=\"onModelChange($event, elem.key)\"\n [isCapitalize]=\"elem.isCapitalize\"\n ></osl-input>\n </div>\n }\n\n @case (\"textarea\") {\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\" [attr.data-field-key]=\"elem.key\">\n <osl-textarea\n [label]=\"elem.label\"\n [skeletonLoading]=\"skeletonLoading\"\n [skeletonTheme]=\"skeletonTheme\"\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\n [rows]=\"elem.textareaRows || 3\"\n [placeholder]=\"elem.placeholder || ''\"\n [maxLength]=\"elem.maxLength ?? null\"\n [minLength]=\"elem.minLength ?? null\"\n [characterCounter]=\"!!elem.characterCounter\"\n [resize]=\"elem.resize || 'none'\"\n (changeEv)=\"elem.change ? elem.change(model) : null\"\n [model]=\"model[elem.key]\"\n (modelChange)=\"onModelChange($event, elem.key)\"\n ></osl-textarea>\n </div>\n }\n\n @case (\"select\") {\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\" [attr.data-field-key]=\"elem.key\">\n <osl-select\n [label]=\"elem.label\"\n [skeletonLoading]=\"skeletonLoading\"\n [skeletonTheme]=\"skeletonTheme\"\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\n [datasource]=\"elem.datasource || []\"\n [displayField]=\"elem.displayField || ''\"\n [valueField]=\"elem.valueField || ''\"\n [placeholder]=\"elem.selectPlaceholder || 'Select...'\"\n [loading]=\"elem.loadingIf ? elem.loadingIf(model) : false\"\n [clearable]=\"!!elem.clearable\"\n (changeEv)=\"elem.change ? onSelectChange(elem, $event) : null\"\n [model]=\"model[elem.key]\"\n (modelChange)=\"onModelChange($event, elem.key)\"\n ></osl-select>\n </div>\n }\n\n @case (\"radio\") {\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\" [attr.data-field-key]=\"elem.key\">\n <osl-radio\n [label]=\"elem.label\"\n [skeletonLoading]=\"skeletonLoading\"\n [skeletonTheme]=\"skeletonTheme\"\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\n [datasource]=\"elem.datasource || []\"\n [displayField]=\"elem.displayField || ''\"\n [valueField]=\"elem.valueField || ''\"\n [inline]=\"!!elem.inline\"\n (changeEv)=\"elem.change ? elem.change(model) : null\"\n [model]=\"model[elem.key]\"\n (modelChange)=\"onModelChange($event, elem.key)\"\n ></osl-radio>\n </div>\n }\n\n @case (\"slide-toggle\") {\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\" [attr.data-field-key]=\"elem.key\">\n <osl-slide-toggle\n [label]=\"elem.label\"\n [skeletonLoading]=\"skeletonLoading\"\n [skeletonTheme]=\"skeletonTheme\"\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\n [labelPosition]=\"elem.labelPosition || 'after'\"\n [trueLabel]=\"elem.trueLabel || ''\"\n [falseLabel]=\"elem.falseLabel || ''\"\n (changeEv)=\"elem.change ? elem.change(model) : null\"\n [model]=\"model[elem.key]\"\n (modelChange)=\"onModelChange($event, elem.key)\"\n ></osl-slide-toggle>\n </div>\n }\n\n @case (\"autocomplete\") {\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\" [attr.data-field-key]=\"elem.key\">\n <osl-autocomplete\n [label]=\"elem.label\"\n [skeletonLoading]=\"skeletonLoading\"\n [skeletonTheme]=\"skeletonTheme\"\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\n [(datasource)]=\"elem.datasource\"\n [displayField]=\"elem.displayField || ''\"\n [valueField]=\"elem.valueField || ''\"\n [placeholder]=\"elem.autocompletePlaceholder || 'Type to search...'\"\n [loading]=\"elem.loadingIf ? elem.loadingIf(model) : false\"\n (changeEv)=\"onFieldChange(elem, $event)\"\n [(model)]=\"model[elem.key]\"\n [methodName]=\"elem.apiMethod\"\n [service]=\"elem.apiService\"\n [object]=\"model[elem.objectName]\"\n [searchType]=\"elem.searchType\"\n [configMethodName]=\"elem.apiConfigMethod\"\n [isLister]=\"elem.isListerAutocomplete\"\n [apiBody]=\"elem.apiBody ? elem.apiBody(model):null\"\n [displayFn]=\"elem.displayFn || null\"\n ></osl-autocomplete>\n </div>\n }\n\n @case (\"file-uploader\") {\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\" [attr.data-field-key]=\"elem.key\">\n <osl-file-upload\n [skeletonLoading]=\"skeletonLoading\"\n [skeletonTheme]=\"skeletonTheme\"\n [label]=\"elem.label\"\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\n [accept]=\"elem.accept || ''\"\n [multiple]=\"!!elem.multiple\"\n [maxSize]=\"elem.maxFileSize || 0\"\n [fileMode]=\"elem.fileMode || 'raw'\"\n [downloadFn]=\"elem.fileDownloadFn ? elem.fileDownloadFn.bind(null, model) : null\"\n (changeEv)=\"elem.change ? elem.change(model) : null\"\n [model]=\"model[elem.key]\"\n (modelChange)=\"onModelChange($event, elem.key)\"\n ></osl-file-upload>\n </div>\n }\n\n @case (\"datepicker\") {\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\" [attr.data-field-key]=\"elem.key\">\n <osl-datepicker\n [skeletonLoading]=\"skeletonLoading\"\n [skeletonTheme]=\"skeletonTheme\"\n [label]=\"elem.label\"\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\n [dateType]=\"elem.dateType || 'date'\"\n [placeholder]=\"elem.placeholder || ''\"\n [minDate]=\"elem.minDateIf ? elem.minDateIf(model) : elem.minDate || ''\"\n [maxDate]=\"elem.maxDateIf ? elem.maxDateIf(model) : elem.maxDate || ''\"\n (changeEv)=\"elem.change ? elem.change(model) : null\"\n [model]=\"model[elem.key]\"\n (modelChange)=\"onModelChange($event, elem.key)\"\n ></osl-datepicker>\n </div>\n }\n\n @case (\"datetimepicker\") {\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\" [attr.data-field-key]=\"elem.key\">\n <osl-datetimepicker\n [skeletonLoading]=\"skeletonLoading\"\n [skeletonTheme]=\"skeletonTheme\"\n [label]=\"elem.label\"\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\n [placeholder]=\"elem.placeholder || ''\"\n [minDatetime]=\"elem.minDatetimeIf ? elem.minDatetimeIf(model) : elem.minDatetime || ''\"\n [maxDatetime]=\"elem.maxDatetimeIf ? elem.maxDatetimeIf(model) : elem.maxDatetime || ''\"\n [format]=\"elem.datetimepickerFormat || 'YYYY-MM-DDTHH:mm'\"\n [showSeconds]=\"!!elem.datetimepickerShowSeconds\"\n [enableMeridian]=\"!!elem.datetimepickerEnableMeridian\"\n (changeEv)=\"elem.change ? elem.change(model) : null\"\n [model]=\"model[elem.key]\"\n (modelChange)=\"onModelChange($event, elem.key)\"\n ></osl-datetimepicker>\n </div>\n }\n\n @case (\"checkbox\") {\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\" [attr.data-field-key]=\"elem.key\">\n <osl-checkbox\n [skeletonLoading]=\"skeletonLoading\"\n [skeletonTheme]=\"skeletonTheme\"\n [label]=\"elem.label\"\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\n [indeterminate]=\"!!elem.indeterminate\"\n (changeEv)=\"elem.change ? elem.change(model) : null\"\n [model]=\"model[elem.key]\"\n (modelChange)=\"onModelChange($event, elem.key)\"\n ></osl-checkbox>\n </div>\n }\n\n @case (\"button\") {\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\" [attr.data-field-key]=\"elem.key\">\n <osl-button\n [label]=\"elem.label\"\n [loading]=\"elem.loadingIf ? elem.loadingIf(model) : false\"\n variant=\"secondary\"\n (clickEv)=\"elem.change ? elem.change(model) : null\"\n ></osl-button>\n </div>\n }\n\n @case (\"fieldset\") {\n <div [class]=\"'col-md-'+elem.columns+' mt-3'\">\n <fieldset class=\"osl-fieldset\">\n @if(elem.label) {\n <legend class=\"osl-fieldset-legend\" [oslSkeleton]=\"skeletonLoading\" [oslSkeletonTheme]=\"skeletonTheme\">\n <span class=\"osl-fieldset-legend__text\">{{ elem.label }}</span>\n </legend>\n }\n <div class=\"row w-100 osl-fieldset-body\">\n @for(innerElem of elem.rows; track innerElem) {\n @if(!(innerElem.hideIf ? innerElem.hideIf(model) : innerElem.hide)) {\n <ng-container *ngTemplateOutlet=\"formField; context: { $implicit: innerElem }\"></ng-container>\n }\n }\n </div>\n </fieldset>\n </div>\n }\n\n @case (\"templateRef\"){\n <div [class]=\"'col-md-'+elem.columns+' mt-3'\">\n <ng-container *ngTemplateOutlet=\"elem.templateRef; context: { $implicit: elem}\"></ng-container>\n </div>\n }\n\n @case (\"spacer\") {\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\" [attr.data-field-key]=\"elem.key\">\n </div>\n }\n\n }\n }\n</ng-template>\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"] }]
|
|
3102
|
+
args: [{ selector: 'osl-dynamic-form', standalone: false, template: "@if(elements && elements.length > 0) {\n <div class=\"row align-items-center w-100\">\n @for(elem of elements; track elem) {\n @if(!(elem.hideIf ? elem.hideIf(model) : elem.hide)) {\n <ng-container *ngTemplateOutlet=\"formField; context: { $implicit: elem }\"></ng-container>\n }\n }\n </div>\n}\n\n<ng-template #formField let-elem>\n @if(elem.hideIf ? !elem.hideIf(model) : true) {\n @switch (elem.elementType) {\n\n @case (\"textbox\") {\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\" [attr.data-field-key]=\"elem.key\">\n <osl-input\n [skeletonLoading]=\"skeletonLoading\"\n [skeletonTheme]=\"skeletonTheme\"\n [label]=\"elem.label\"\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\n [type]=\"elem.inputType || 'text'\"\n [placeholder]=\"elem.placeholder || ''\"\n [mask]=\"elem.mask || ''\"\n [min]=\"elem.min ?? ''\"\n [max]=\"elem.max ?? ''\"\n [minLength]=\"elem.minLength ?? null\"\n [maxLength]=\"elem.maxLength ?? null\"\n [prefixIcon]=\"elem.prefixIcon || ''\"\n [suffixIcon]=\"elem.suffixIcon || ''\"\n [suffixRef]=\"elem.suffixRef || null\"\n [onlyChars]=\"!!elem.onlyChars\"\n [decimalPortion]=\"elem.decimalPortion ?? null\"\n [allowNegative]=\"!!elem.allowNegative\"\n (changeEv)=\"elem.change ? elem.change(model) : null\"\n [model]=\"model[elem.key]\"\n (modelChange)=\"onModelChange($event, elem.key)\"\n [isCapitalize]=\"elem.isCapitalize\"\n ></osl-input>\n </div>\n }\n\n @case (\"textarea\") {\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\" [attr.data-field-key]=\"elem.key\">\n <osl-textarea\n [label]=\"elem.label\"\n [skeletonLoading]=\"skeletonLoading\"\n [skeletonTheme]=\"skeletonTheme\"\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\n [rows]=\"elem.textareaRows || 3\"\n [placeholder]=\"elem.placeholder || ''\"\n [maxLength]=\"elem.maxLength ?? null\"\n [minLength]=\"elem.minLength ?? null\"\n [characterCounter]=\"!!elem.characterCounter\"\n [resize]=\"elem.resize || 'none'\"\n (changeEv)=\"elem.change ? elem.change(model) : null\"\n [model]=\"model[elem.key]\"\n (modelChange)=\"onModelChange($event, elem.key)\"\n ></osl-textarea>\n </div>\n }\n\n @case (\"select\") {\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\" [attr.data-field-key]=\"elem.key\">\n <osl-select\n [label]=\"elem.label\"\n [skeletonLoading]=\"skeletonLoading\"\n [skeletonTheme]=\"skeletonTheme\"\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\n [datasource]=\"elem.datasource || []\"\n [displayField]=\"elem.displayField || ''\"\n [valueField]=\"elem.valueField || ''\"\n [placeholder]=\"elem.selectPlaceholder || 'Select...'\"\n [loading]=\"elem.loadingIf ? elem.loadingIf(model) : false\"\n [clearable]=\"!!elem.clearable\"\n (changeEv)=\"elem.change ? onSelectChange(elem, $event) : null\"\n [model]=\"model[elem.key]\"\n (modelChange)=\"onModelChange($event, elem.key)\"\n ></osl-select>\n </div>\n }\n\n @case (\"radio\") {\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\" [attr.data-field-key]=\"elem.key\">\n <osl-radio\n [label]=\"elem.label\"\n [skeletonLoading]=\"skeletonLoading\"\n [skeletonTheme]=\"skeletonTheme\"\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\n [datasource]=\"elem.datasource || []\"\n [displayField]=\"elem.displayField || ''\"\n [valueField]=\"elem.valueField || ''\"\n [inline]=\"!!elem.inline\"\n (changeEv)=\"elem.change ? elem.change(model) : null\"\n [model]=\"model[elem.key]\"\n (modelChange)=\"onModelChange($event, elem.key)\"\n ></osl-radio>\n </div>\n }\n\n @case (\"slide-toggle\") {\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\" [attr.data-field-key]=\"elem.key\">\n <osl-slide-toggle\n [label]=\"elem.label\"\n [skeletonLoading]=\"skeletonLoading\"\n [skeletonTheme]=\"skeletonTheme\"\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\n [labelPosition]=\"elem.labelPosition || 'after'\"\n [trueLabel]=\"elem.trueLabel || ''\"\n [falseLabel]=\"elem.falseLabel || ''\"\n (changeEv)=\"elem.change ? elem.change(model) : null\"\n [model]=\"model[elem.key]\"\n (modelChange)=\"onModelChange($event, elem.key)\"\n ></osl-slide-toggle>\n </div>\n }\n\n @case (\"autocomplete\") {\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\" [attr.data-field-key]=\"elem.key\">\n <osl-autocomplete\n [label]=\"elem.label\"\n [skeletonLoading]=\"skeletonLoading\"\n [skeletonTheme]=\"skeletonTheme\"\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\n [(datasource)]=\"elem.datasource\"\n [displayField]=\"elem.displayField || ''\"\n [valueField]=\"elem.valueField || ''\"\n [placeholder]=\"elem.autocompletePlaceholder || 'Type to search...'\"\n [loading]=\"elem.loadingIf ? elem.loadingIf(model) : false\"\n (changeEv)=\"onFieldChange(elem, $event)\"\n [(model)]=\"model[elem.key]\"\n [methodName]=\"elem.apiMethod\"\n [service]=\"elem.apiService\"\n [object]=\"model[elem.objectName]\"\n [searchType]=\"elem.searchType\"\n [configMethodName]=\"elem.apiConfigMethod\"\n [isLister]=\"elem.isListerAutocomplete\"\n [apiBody]=\"elem.apiBody ? elem.apiBody(model):null\"\n [displayFn]=\"elem.displayFn || null\"\n ></osl-autocomplete>\n </div>\n }\n\n @case (\"file-uploader\") {\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\" [attr.data-field-key]=\"elem.key\">\n <osl-file-upload\n [skeletonLoading]=\"skeletonLoading\"\n [skeletonTheme]=\"skeletonTheme\"\n [label]=\"elem.label\"\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\n [accept]=\"elem.accept || ''\"\n [multiple]=\"!!elem.multiple\"\n [maxSize]=\"elem.maxFileSize || 0\"\n [fileMode]=\"elem.fileMode || 'raw'\"\n [downloadFn]=\"elem.fileDownloadFn ? elem.fileDownloadFn.bind(null, model) : null\"\n (changeEv)=\"elem.change ? elem.change(model) : null\"\n [model]=\"model[elem.key]\"\n (modelChange)=\"onModelChange($event, elem.key)\"\n ></osl-file-upload>\n </div>\n }\n\n @case (\"datepicker\") {\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\" [attr.data-field-key]=\"elem.key\">\n <osl-datepicker\n [skeletonLoading]=\"skeletonLoading\"\n [skeletonTheme]=\"skeletonTheme\"\n [label]=\"elem.label\"\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\n [dateType]=\"elem.dateType || 'date'\"\n [placeholder]=\"elem.placeholder || ''\"\n [minDate]=\"elem.minDateIf ? elem.minDateIf(model) : elem.minDate || ''\"\n [maxDate]=\"elem.maxDateIf ? elem.maxDateIf(model) : elem.maxDate || ''\"\n (changeEv)=\"elem.change ? elem.change(model) : null\"\n [model]=\"model[elem.key]\"\n (modelChange)=\"onModelChange($event, elem.key)\"\n ></osl-datepicker>\n </div>\n }\n\n @case (\"datetimepicker\") {\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\" [attr.data-field-key]=\"elem.key\">\n <osl-datetimepicker\n [skeletonLoading]=\"skeletonLoading\"\n [skeletonTheme]=\"skeletonTheme\"\n [label]=\"elem.label\"\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\n [placeholder]=\"elem.placeholder || ''\"\n [minDatetime]=\"elem.minDatetimeIf ? elem.minDatetimeIf(model) : elem.minDatetime || ''\"\n [maxDatetime]=\"elem.maxDatetimeIf ? elem.maxDatetimeIf(model) : elem.maxDatetime || ''\"\n [format]=\"elem.datetimepickerFormat || 'YYYY-MM-DDTHH:mm'\"\n [showSeconds]=\"!!elem.datetimepickerShowSeconds\"\n [enableMeridian]=\"!!elem.datetimepickerEnableMeridian\"\n (changeEv)=\"elem.change ? elem.change(model) : null\"\n [model]=\"model[elem.key]\"\n (modelChange)=\"onModelChange($event, elem.key)\"\n ></osl-datetimepicker>\n </div>\n }\n\n @case (\"checkbox\") {\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\" [attr.data-field-key]=\"elem.key\">\n <osl-checkbox\n [skeletonLoading]=\"skeletonLoading\"\n [skeletonTheme]=\"skeletonTheme\"\n [label]=\"elem.label\"\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\n [indeterminate]=\"!!elem.indeterminate\"\n (changeEv)=\"elem.change ? elem.change(model) : null\"\n [model]=\"model[elem.key]\"\n (modelChange)=\"onModelChange($event, elem.key)\"\n ></osl-checkbox>\n </div>\n }\n\n @case (\"button\") {\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\" [attr.data-field-key]=\"elem.key\">\n <osl-button\n [label]=\"elem.label\"\n [loading]=\"elem.loadingIf ? elem.loadingIf(model) : false\"\n variant=\"secondary\"\n (clickEv)=\"elem.change ? elem.change(model) : null\"\n ></osl-button>\n </div>\n }\n\n @case (\"fieldset\") {\n <div [class]=\"'col-md-'+elem.columns+' mt-3'\">\n <fieldset class=\"osl-fieldset\">\n @if(elem.label) {\n <legend class=\"osl-fieldset-legend\" [oslSkeleton]=\"skeletonLoading\" [oslSkeletonTheme]=\"skeletonTheme\">\n <span class=\"osl-fieldset-legend__text\">{{ elem.label }}</span>\n </legend>\n }\n <div class=\"row w-100 osl-fieldset-body\">\n @for(innerElem of elem.rows; track innerElem) {\n @if(!(innerElem.hideIf ? innerElem.hideIf(model) : innerElem.hide)) {\n <ng-container *ngTemplateOutlet=\"formField; context: { $implicit: innerElem }\"></ng-container>\n }\n }\n </div>\n </fieldset>\n </div>\n }\n\n @case (\"templateRef\"){\n <div [class]=\"'col-md-'+elem.columns+' mt-3'\">\n <ng-container *ngTemplateOutlet=\"elem.templateRef; context: { $implicit: elem}\"></ng-container>\n </div>\n }\n\n @case (\"spacer\") {\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\" [attr.data-field-key]=\"elem.key\">\n </div>\n }\n\n }\n }\n</ng-template>\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"] }]
|
|
3077
3103
|
}], ctorParameters: () => [{ type: i0.ChangeDetectorRef }], propDecorators: { elements: [{
|
|
3078
3104
|
type: Input,
|
|
3079
3105
|
args: ['elements']
|
|
@@ -4198,6 +4224,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
4198
4224
|
|
|
4199
4225
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
4200
4226
|
class OslFormGrid {
|
|
4227
|
+
datePipe;
|
|
4228
|
+
constructor(datePipe) {
|
|
4229
|
+
this.datePipe = datePipe;
|
|
4230
|
+
}
|
|
4201
4231
|
// ── Existing inputs ────────────────────────────────────────────────────────
|
|
4202
4232
|
columns = [];
|
|
4203
4233
|
datasource = [];
|
|
@@ -4351,6 +4381,50 @@ class OslFormGrid {
|
|
|
4351
4381
|
colRequired(col) {
|
|
4352
4382
|
return !!col.formElem?.required || !!col.formElem?.requiredIf;
|
|
4353
4383
|
}
|
|
4384
|
+
/** Resolves the human-readable value for a cell — same value the grid control represents,
|
|
4385
|
+
* not the raw model value (e.g. displayField label instead of an id, formatted date, etc). */
|
|
4386
|
+
getPreviewValue(col, row) {
|
|
4387
|
+
const elem = col.formElem;
|
|
4388
|
+
const raw = row[col.key];
|
|
4389
|
+
if (!elem)
|
|
4390
|
+
return raw ?? '—';
|
|
4391
|
+
switch (elem.elementType) {
|
|
4392
|
+
case 'select':
|
|
4393
|
+
case 'autocomplete':
|
|
4394
|
+
case 'radio': {
|
|
4395
|
+
if (!elem.datasource?.length || raw === null || raw === undefined)
|
|
4396
|
+
return raw ?? '—';
|
|
4397
|
+
const resolve = (v) => {
|
|
4398
|
+
const item = elem.datasource.find(d => (elem.valueField ? d[elem.valueField] : d) === v);
|
|
4399
|
+
if (!item)
|
|
4400
|
+
return v;
|
|
4401
|
+
if (elem.displayFn)
|
|
4402
|
+
return elem.displayFn(item);
|
|
4403
|
+
return elem.displayField ? item[elem.displayField] : item;
|
|
4404
|
+
};
|
|
4405
|
+
return Array.isArray(raw) ? raw.map(resolve).join(', ') || '—' : resolve(raw);
|
|
4406
|
+
}
|
|
4407
|
+
case 'datepicker':
|
|
4408
|
+
return this.formatDate(raw, elem.dateType === 'month' ? 'MMM y' : elem.dateType === 'time' ? 'shortTime' : 'mediumDate');
|
|
4409
|
+
case 'datetimepicker':
|
|
4410
|
+
return this.formatDate(raw, 'medium');
|
|
4411
|
+
case 'checkbox':
|
|
4412
|
+
case 'slide-toggle':
|
|
4413
|
+
return raw ? (elem.trueLabel || 'Yes') : (elem.falseLabel || 'No');
|
|
4414
|
+
default:
|
|
4415
|
+
return raw ?? '—';
|
|
4416
|
+
}
|
|
4417
|
+
}
|
|
4418
|
+
formatDate(raw, format) {
|
|
4419
|
+
if (!raw)
|
|
4420
|
+
return '—';
|
|
4421
|
+
try {
|
|
4422
|
+
return this.datePipe.transform(raw, format) ?? raw;
|
|
4423
|
+
}
|
|
4424
|
+
catch {
|
|
4425
|
+
return raw;
|
|
4426
|
+
}
|
|
4427
|
+
}
|
|
4354
4428
|
onSelectChange(col, row, i, value) {
|
|
4355
4429
|
if (!col.formElem?.change)
|
|
4356
4430
|
return;
|
|
@@ -4523,13 +4597,13 @@ class OslFormGrid {
|
|
|
4523
4597
|
currentContainer: target,
|
|
4524
4598
|
});
|
|
4525
4599
|
}
|
|
4526
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: OslFormGrid, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4527
|
-
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", enableDragDrop: "enableDragDrop", draggingList: "draggingList", draggingColumns: "draggingColumns", leftPanelWidth: "leftPanelWidth", dragTitle: "dragTitle", showSearch: "showSearch", allowReorder: "allowReorder", allowRemove: "allowRemove", reorderDisabledIf: "reorderDisabledIf", panelSearchType: "panelSearchType", panelLoading: "panelLoading", panelTotalRecords: "panelTotalRecords", panelPageSize: "panelPageSize" }, outputs: { datasourceChange: "datasourceChange", rowAdd: "rowAdd", rowDelete: "rowDelete", dragStarted: "dragStarted", dragEnded: "dragEnded", dragEntered: "dragEntered", dragExited: "dragExited", itemDropped: "itemDropped", itemMoved: "itemMoved", itemRemoved: "itemRemoved", itemSelected: "itemSelected", gridChanged: "gridChanged", panelSearch: "panelSearch", panelPageChange: "panelPageChange" }, ngImport: i0, template: "<div class=\"osl-fg-wrapper\" [class.osl-fg-wrapper--builder]=\"enableDragDrop\">\n\n <!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\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 Available Controls panel (only in builder mode)\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\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\n\n <!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\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 Main grid area (always present)\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\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-fg-grid-area\">\n\n <!-- Panel-to-grid drop overlay: always in DOM so #gridDropZone is always resolvable.\n pointer-events: none by default; only activates visually when isDraggingFromPanel.\n CDK hit-tests by bounding rect, not pointer-events, so drops are captured regardless. -->\n <div class=\"osl-fg-table-drop-zone\"\n [class.osl-fg-table-drop-zone--active]=\"isDraggingFromPanel\"\n cdkDropList\n #gridDropZone=\"cdkDropList\"\n [cdkDropListSortingDisabled]=\"true\"\n [cdkDropListDisabled]=\"!enableDragDrop\"\n [cdkDropListData]=\"[]\"\n (cdkDropListDropped)=\"onPanelDrop($event)\">\n @if (isDraggingFromPanel) {\n <div class=\"osl-fg-drop-zone-badge\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\" width=\"13\" height=\"13\">\n <path d=\"M12 5v14M5 12l7 7 7-7\"/>\n </svg>\n Drop to Add\n </div>\n }\n </div>\n\n <div class=\"osl-fg-table-container\"\n [style.height]=\"tableHeight\"\n [class.osl-fg-table-container--drop-active]=\"isDragging && enableDragDrop\">\n\n <table class=\"osl-fg-table\">\n\n <!-- \u2500\u2500 Header \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <thead class=\"osl-fg-thead\">\n <tr>\n @if (allowReorder) {\n <th class=\"osl-fg-th osl-fg-th--drag\"></th>\n }\n @if (hasActions) {\n <th class=\"osl-fg-th osl-fg-th--actions\">\n @if (canAdd) {\n <button class=\"osl-grid-icon-btn\" (click)=\"addRow()\" [disabled]=\"loading\" title=\"Add row\">\n <mat-icon>add</mat-icon>\n </button>\n }\n </th>\n }\n @for (col of columns; track col.key) {\n <th class=\"osl-fg-th\" [style.width]=\"col.width\">\n {{ col.displayName }}\n @if (colRequired(col)) {\n <span class=\"osl-fg-th-required\">*</span>\n }\n </th>\n }\n </tr>\n </thead>\n\n <!-- \u2500\u2500 Body \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 <tbody\n cdkDropList\n #gridList=\"cdkDropList\"\n [cdkDropListData]=\"pagedData\"\n [cdkDropListDisabled]=\"!allowReorder\"\n (cdkDropListDropped)=\"onGridReorder($event)\">\n\n <!-- Loading state -->\n @if (loading) {\n @for (sk of skeletonRows; track $index) {\n <tr class=\"osl-fg-row osl-fg-row--skeleton\">\n @if (allowReorder) {\n <td class=\"osl-fg-td osl-fg-td--drag\"></td>\n }\n @if (hasActions) {\n <td class=\"osl-fg-td osl-fg-td--actions\">\n <div class=\"osl-fg-skeleton osl-fg-skeleton--sm\"></div>\n </td>\n }\n @for (col of columns; track col.key) {\n <td class=\"osl-fg-td\"><div class=\"osl-fg-skeleton\"></div></td>\n }\n </tr>\n }\n\n <!-- Empty state -->\n } @else if (pagedData.length === 0) {\n <tr>\n <td [attr.colspan]=\"columns.length + (hasActions ? 1 : 0) + (allowReorder ? 1 : 0)\"\n class=\"osl-fg-empty\">\n <div class=\"osl-fg-empty-inner\">\n @if (enableDragDrop && isDragging) {\n <svg width=\"36\" height=\"36\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\">\n <path d=\"M12 5v14M5 12l7 7 7-7\"/>\n </svg>\n <p>Drop here</p>\n } @else {\n <svg width=\"36\" height=\"36\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\">\n <rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"2\"/>\n <line x1=\"3\" y1=\"9\" x2=\"21\" y2=\"9\"/><line x1=\"9\" y1=\"9\" x2=\"9\" y2=\"21\"/>\n </svg>\n <p>No rows yet.{{ canAdd ? ' Use + to get started.' : (enableDragDrop ? ' Drag a control here.' : '') }}</p>\n }\n </div>\n </td>\n </tr>\n\n <!-- Data rows -->\n } @else {\n @for (row of pagedData; track $index; let i = $index) {\n <tr class=\"osl-fg-row\"\n cdkDrag\n [cdkDragData]=\"row\"\n [cdkDragDisabled]=\"!allowReorder || isReorderDisabled(row)\"\n (cdkDragStarted)=\"onDragStartedEvent($event, row, 'grid')\"\n (cdkDragEnded)=\"onDragEndedEvent($event, row, 'grid')\"\n (click)=\"enableDragDrop ? selectGridItem(row, i) : null\">\n\n <!-- Drag handle -->\n @if (allowReorder) {\n <td class=\"osl-fg-td osl-fg-td--drag\">\n <span class=\"osl-fg-row-handle\"\n [class.osl-fg-row-handle--disabled]=\"isReorderDisabled(row)\"\n cdkDragHandle\n [cdkDragHandleDisabled]=\"isReorderDisabled(row)\"\n [title]=\"isReorderDisabled(row) ? 'Reorder disabled' : 'Drag to reorder'\">\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <circle cx=\"9\" cy=\"5\" r=\"1.3\"/><circle cx=\"9\" cy=\"12\" r=\"1.3\"/><circle cx=\"9\" cy=\"19\" r=\"1.3\"/>\n <circle cx=\"15\" cy=\"5\" r=\"1.3\"/><circle cx=\"15\" cy=\"12\" r=\"1.3\"/><circle cx=\"15\" cy=\"19\" r=\"1.3\"/>\n </svg>\n </span>\n </td>\n }\n\n <!-- Action cell (remove / delete) -->\n @if (hasActions) {\n <td class=\"osl-fg-td osl-fg-td--actions\">\n @if (enableDragDrop && allowRemove) {\n <button class=\"osl-grid-icon-btn osl-grid-icon-btn--danger\"\n (click)=\"removeGridRow(i); $event.stopPropagation()\" title=\"Remove\">\n <mat-icon>close</mat-icon>\n </button>\n } @else if (!enableDragDrop && canDelete) {\n <button class=\"osl-grid-icon-btn osl-grid-icon-btn--danger\"\n (click)=\"deleteRow(i)\" title=\"Delete\">\n <mat-icon>delete_outline</mat-icon>\n </button>\n }\n </td>\n }\n\n <!-- Data cells -->\n @for (col of columns; track col.key) {\n <td class=\"osl-fg-td\" [style.width]=\"col.width\">\n @if (col.formElem) {\n <div class=\"osl-fg-cell-form\" [style.width]=\"col.width\">\n @switch (col.formElem.elementType) {\n\n @case ('textbox') {\n <osl-input\n [label]=\"''\"\n [required]=\"false\"\n [disabled]=\"isDisabled(col.formElem,row,i)\"\n [type]=\"col.formElem.inputType || 'text'\"\n [placeholder]=\"col.formElem.placeholder || col.displayName\"\n [mask]=\"col.formElem.mask || ''\"\n [min]=\"col.formElem.min ?? ''\"\n [max]=\"col.formElem.max ?? ''\"\n [minLength]=\"col.formElem.minLength ?? null\"\n [maxLength]=\"col.formElem.maxLength ?? null\"\n [prefixIcon]=\"col.formElem.prefixIcon || ''\"\n [suffixIcon]=\"col.formElem.suffixIcon || ''\"\n [decimalPortion]=\"col.formElem.decimalPortion ?? null\"\n [model]=\"row[col.key]\"\n (modelChange)=\"onCellChange(row, col, $event)\"\n (changeEv)=\"col.formElem.change ? col.formElem.change(row,i) : null\">\n </osl-input>\n }\n\n @case ('textarea') {\n <osl-textarea\n [label]=\"''\"\n [required]=\"false\"\n [disabled]=\"isDisabled(col.formElem,row,i)\"\n [rows]=\"col.formElem.textareaRows || 3\"\n [placeholder]=\"col.formElem.placeholder || col.displayName\"\n [maxLength]=\"col.formElem.maxLength ?? null\"\n [minLength]=\"col.formElem.minLength ?? null\"\n [characterCounter]=\"!!col.formElem.characterCounter\"\n [resize]=\"col.formElem.resize || 'none'\"\n [model]=\"row[col.key]\"\n (modelChange)=\"onCellChange(row, col, $event)\"\n (changeEv)=\"col.formElem.change ? col.formElem.change(row,i) : null\">\n </osl-textarea>\n }\n\n @case ('select') {\n <osl-select\n [label]=\"''\"\n [required]=\"false\"\n [disabled]=\"isDisabled(col.formElem,row,i)\"\n [datasource]=\"col.formElem.datasource || []\"\n [displayField]=\"col.formElem.displayField || ''\"\n [valueField]=\"col.formElem.valueField || ''\"\n [placeholder]=\"col.formElem.selectPlaceholder || col.displayName\"\n [loading]=\"isLoading(row, col.formElem)\"\n [clearable]=\"!!col.formElem.clearable\"\n [model]=\"row[col.key]\"\n (modelChange)=\"onCellChange(row, col, $event)\"\n (changeEv)=\"col.formElem.change ? onSelectChange(col, row, i, $event) : null\">\n </osl-select>\n }\n\n @case ('autocomplete') {\n <osl-autocomplete\n [label]=\"''\"\n [required]=\"false\"\n [disabled]=\"isDisabled(col.formElem,row,i)\"\n [datasource]=\"col.formElem.datasource || []\"\n [displayField]=\"col.formElem.displayField || ''\"\n [valueField]=\"col.formElem.valueField || ''\"\n [placeholder]=\"col.formElem.autocompletePlaceholder || col.displayName\"\n [loading]=\"isLoading(row, col.formElem)\"\n [searchType]=\"col.formElem.searchType ? col.formElem.searchType : 'Local'\"\n [methodName]=\"col.formElem.apiMethod || ''\"\n [configMethodName]=\"col.formElem.apiConfigMethod || ''\"\n [service]=\"col.formElem.apiService\"\n [object]=\"col.formElem.objectName ? row[col.formElem.objectName] : null\"\n [isLister]=\"!!col.formElem.isListerAutocomplete\"\n [model]=\"row[col.key]\"\n (modelChange)=\"onCellChange(row, col, $event)\"\n (changeEv)=\"col.formElem.change ? onSelectChange(col, row, i, $event) : null\">\n </osl-autocomplete>\n }\n\n @case ('radio') {\n <osl-radio\n [label]=\"''\"\n [required]=\"false\"\n [disabled]=\"isDisabled(col.formElem,row,i)\"\n [datasource]=\"col.formElem.datasource || []\"\n [displayField]=\"col.formElem.displayField || ''\"\n [valueField]=\"col.formElem.valueField || ''\"\n [inline]=\"!!col.formElem.inline\"\n [model]=\"row[col.key]\"\n (modelChange)=\"onCellChange(row, col, $event)\"\n (changeEv)=\"col.formElem.change ? col.formElem.change(row,i) : null\">\n </osl-radio>\n }\n\n @case ('checkbox') {\n <osl-checkbox\n [label]=\"''\"\n [required]=\"false\"\n [disabled]=\"isDisabled(col.formElem,row,i)\"\n [indeterminate]=\"!!col.formElem.indeterminate\"\n [model]=\"row[col.key]\"\n (modelChange)=\"onCellChange(row, col, $event)\"\n (changeEv)=\"col.formElem.change ? col.formElem.change(row,i) : null\">\n </osl-checkbox>\n }\n\n @case ('slide-toggle') {\n <osl-slide-toggle\n [label]=\"''\"\n [disabled]=\"isDisabled(col.formElem,row,i)\"\n [labelPosition]=\"col.formElem.labelPosition || 'after'\"\n [trueLabel]=\"col.formElem.trueLabel || ''\"\n [falseLabel]=\"col.formElem.falseLabel || ''\"\n [model]=\"row[col.key]\"\n (modelChange)=\"onCellChange(row, col, $event)\"\n (changeEv)=\"col.formElem.change ? col.formElem.change(row,i) : null\">\n </osl-slide-toggle>\n }\n\n @case ('datepicker') {\n <osl-datepicker\n [label]=\"''\"\n [required]=\"false\"\n [disabled]=\"isDisabled(col.formElem,row,i)\"\n [dateType]=\"col.formElem.dateType || 'date'\"\n [placeholder]=\"col.formElem.placeholder || col.displayName\"\n [minDate]=\"col.formElem.minDate || ''\"\n [maxDate]=\"col.formElem.maxDate || ''\"\n [model]=\"row[col.key]\"\n (modelChange)=\"onCellChange(row, col, $event)\"\n (changeEv)=\"col.formElem.change ? col.formElem.change(row,i) : null\">\n </osl-datepicker>\n }\n\n @case ('datetimepicker') {\n <osl-datetimepicker\n [label]=\"''\"\n [required]=\"false\"\n [disabled]=\"isDisabled(col.formElem,row,i)\"\n [placeholder]=\"col.formElem.placeholder || col.displayName\"\n [minDatetime]=\"col.formElem.minDatetime || ''\"\n [maxDatetime]=\"col.formElem.maxDatetime || ''\"\n [format]=\"col.formElem.datetimepickerFormat || 'YYYY-MM-DDTHH:mm'\"\n [showSeconds]=\"!!col.formElem.datetimepickerShowSeconds\"\n [enableMeridian]=\"!!col.formElem.datetimepickerEnableMeridian\"\n [model]=\"row[col.key]\"\n (modelChange)=\"onCellChange(row, col, $event)\"\n (changeEv)=\"col.formElem.change ? col.formElem.change(row,i) : null\">\n </osl-datetimepicker>\n }\n\n @case ('file-uploader') {\n <osl-file-upload\n [label]=\"''\"\n [required]=\"false\"\n [disabled]=\"isDisabled(col.formElem,row,i)\"\n [accept]=\"col.formElem.accept || ''\"\n [multiple]=\"!!col.formElem.multiple\"\n [maxSize]=\"col.formElem.maxFileSize || 0\"\n [model]=\"row[col.key]\"\n (modelChange)=\"onCellChange(row, col, $event)\"\n (changeEv)=\"col.formElem.change ? col.formElem.change(row,i) : null\">\n </osl-file-upload>\n }\n\n @case ('templateRef') {\n <ng-container *ngTemplateOutlet=\"col.formElem.templateRef; context: { $implicit: row, index: i }\"></ng-container>\n }\n\n }\n </div>\n } @else {\n <span class=\"osl-fg-cell-text\">{{ row[col.key] ?? '--' }}</span>\n }\n </td>\n }\n\n <!-- Row drag preview -->\n <ng-template cdkDragPreview>\n <div class=\"osl-fg-row-preview\">\n @for (col of columns; track col.key) {\n <span class=\"osl-fg-row-preview__cell\">{{ row[col.key] ?? '\u2014' }}</span>\n }\n </div>\n </ng-template>\n\n <!-- Drop placeholder: spans full row width with \"Drop Here\" -->\n <ng-template cdkDragPlaceholder>\n <tr class=\"osl-fg-row-placeholder\">\n <td [attr.colspan]=\"columns.length + (hasActions ? 1 : 0) + (allowReorder ? 1 : 0)\">\n <div class=\"osl-fg-drop-here\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M12 5v14M5 12l7 7 7-7\"/>\n </svg>\n Drop Here\n </div>\n </td>\n </tr>\n </ng-template>\n\n </tr>\n }\n }\n\n </tbody>\n\n <!-- \u2500\u2500 Footer \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 (footerColumns && footerColumns.length > 0 && datasource && datasource.length > 0) {\n <tfoot class=\"osl-fg-tfoot\">\n <tr class=\"osl-fg-footer-row\">\n @for (fcol of footerColumns; track $index) {\n <td\n [class]=\"'osl-fg-footer-td ' + (fcol.class || '')\"\n [attr.colspan]=\"fcol.colspan || 1\">\n {{ fcol.displayFn ? fcol.displayFn(datasource) : (fcol.display || '') }}\n </td>\n }\n </tr>\n </tfoot>\n }\n\n </table>\n </div>\n\n <!-- \u2500\u2500 Pagination (unchanged) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 (isPaginated) {\n <div class=\"osl-fg-pagination\">\n\n <span class=\"osl-fg-pagination__info\">\n @if (loading) {\n Loading...\n } @else if (_total > 0) {\n {{ startRecord }}\u2013{{ endRecord }} of {{ _total }} rows\n } @else {\n No rows\n }\n </span>\n\n <div class=\"osl-fg-pagination__controls\">\n <button class=\"osl-fg-page-btn osl-fg-page-btn--nav\" (click)=\"goToPage(1)\" [disabled]=\"currentPage === 1 || loading\">\u00AB</button>\n <button class=\"osl-fg-page-btn osl-fg-page-btn--nav\" (click)=\"goToPage(currentPage - 1)\" [disabled]=\"currentPage === 1 || loading\">\u2039 Prev</button>\n\n @for (page of pageNumbers; track $index) {\n @if (page === -1) {\n <span class=\"osl-fg-page-ellipsis\">\u2026</span>\n } @else {\n <button\n class=\"osl-fg-page-btn osl-fg-page-btn--num\"\n [class.osl-fg-page-btn--active]=\"page === currentPage\"\n [disabled]=\"loading\"\n (click)=\"goToPage(page)\">\n {{ page }}\n </button>\n }\n }\n\n <button class=\"osl-fg-page-btn osl-fg-page-btn--nav\" (click)=\"goToPage(currentPage + 1)\" [disabled]=\"currentPage === totalPages || loading\">Next \u203A</button>\n <button class=\"osl-fg-page-btn osl-fg-page-btn--nav\" (click)=\"goToPage(totalPages)\" [disabled]=\"currentPage === totalPages || loading\">\u00BB</button>\n </div>\n\n <div class=\"osl-fg-pagination__size\">\n <select class=\"osl-fg-page-size\" [ngModel]=\"pageSize\" (ngModelChange)=\"onPageSizeChange($event)\" [disabled]=\"loading\">\n @for (opt of pageSizeOptions; track opt) {\n <option [value]=\"opt\">{{ opt }} per page</option>\n }\n </select>\n </div>\n\n </div>\n }\n\n </div><!-- /.osl-fg-grid-area -->\n @if (enableDragDrop) {\n <div class=\"osl-fg-panel\" [style.width.px]=\"leftPanelWidth\" [style.height]=\"tableHeight\">\n\n <!-- Panel header -->\n <div class=\"osl-fg-panel__header\">\n <span class=\"osl-fg-panel__title\">{{ dragTitle }}</span>\n @if (panelSearchType === 'Api' && panelTotalRecords > 0) {\n <span class=\"osl-fg-panel__count\">{{ draggingList.length }}/{{ panelTotalRecords }}</span>\n } @else if (filteredDraggingList.length > 0) {\n <span class=\"osl-fg-panel__count\">{{ filteredDraggingList.length }}</span>\n }\n </div>\n\n <!-- Search box -->\n @if (showSearch) {\n <div class=\"osl-fg-panel__search\">\n <svg class=\"osl-fg-search-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <circle cx=\"11\" cy=\"11\" r=\"8\"/><line x1=\"21\" y1=\"21\" x2=\"16.65\" y2=\"16.65\"/>\n </svg>\n <input\n class=\"osl-fg-search-input\"\n type=\"text\"\n [ngModel]=\"dragSearchQuery\"\n (ngModelChange)=\"onPanelSearchChange($event)\"\n placeholder=\"Search\u2026\"\n autocomplete=\"off\" />\n @if (dragSearchQuery) {\n <button class=\"osl-fg-search-clear\" (click)=\"clearPanelSearch()\" title=\"Clear\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"/><line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"/>\n </svg>\n </button>\n }\n </div>\n }\n\n <!-- Item list -->\n <div class=\"osl-fg-panel__list\"\n cdkDropList\n #availableList=\"cdkDropList\"\n [cdkDropListData]=\"filteredDraggingList\"\n [cdkDropListConnectedTo]=\"[gridDropZone]\"\n [cdkDropListSortingDisabled]=\"true\"\n (cdkDropListEntered)=\"onDragEnteredEvent($event, 'available')\"\n (cdkDropListExited)=\"onDragExitedEvent($event, 'available')\">\n\n <!-- Loading skeleton -->\n @if (panelLoading && draggingList.length === 0) {\n @for (sk of [1,2,3,4,5]; track sk) {\n <div class=\"osl-fg-item-skeleton\"></div>\n }\n\n } @else if (filteredDraggingList.length === 0) {\n <div class=\"osl-fg-panel__empty\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\">\n <circle cx=\"11\" cy=\"11\" r=\"8\"/><line x1=\"21\" y1=\"21\" x2=\"16.65\" y2=\"16.65\"/>\n </svg>\n <span>{{ dragSearchQuery ? 'No results found' : 'No results available' }}</span>\n </div>\n\n } @else {\n @for (item of filteredDraggingList; track item.id) {\n <div class=\"osl-fg-drag-item\"\n cdkDrag\n [cdkDragData]=\"item\"\n (cdkDragStarted)=\"onDragStartedEvent($event, item, 'available')\"\n (cdkDragEnded)=\"onDragEndedEvent($event, item, 'available')\">\n\n <!-- Grip handle -->\n <span class=\"osl-fg-drag-item__grip\">\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <circle cx=\"9\" cy=\"5\" r=\"1.4\"/><circle cx=\"9\" cy=\"12\" r=\"1.4\"/><circle cx=\"9\" cy=\"19\" r=\"1.4\"/>\n <circle cx=\"15\" cy=\"5\" r=\"1.4\"/><circle cx=\"15\" cy=\"12\" r=\"1.4\"/><circle cx=\"15\" cy=\"19\" r=\"1.4\"/>\n </svg>\n </span>\n\n <!-- Label + description -->\n <div class=\"osl-fg-drag-item__body\">\n <span class=\"osl-fg-drag-item__label\">{{ item.label }}</span>\n @if (item.description) {\n <span class=\"osl-fg-drag-item__desc\">{{ item.description }}</span>\n }\n </div>\n\n <!-- Drag-into-grid hint -->\n <span class=\"osl-fg-drag-item__arrow\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <line x1=\"5\" y1=\"12\" x2=\"19\" y2=\"12\"/><polyline points=\"12 5 19 12 12 19\"/>\n </svg>\n </span>\n\n <!-- Placeholder shown in panel while card is dragged -->\n <div *cdkDragPlaceholder class=\"osl-fg-item-placeholder\"></div>\n\n <!-- Preview under the cursor while dragging -->\n <ng-template cdkDragPreview>\n <div class=\"osl-fg-drag-preview\">{{ item.label }}</div>\n </ng-template>\n\n </div>\n }\n\n <!-- Load More (API mode) -->\n @if (panelHasMore) {\n <button class=\"osl-fg-load-more\" (click)=\"loadMorePanelItems()\" [disabled]=\"panelLoading\">\n @if (panelLoading) {\n <span class=\"osl-fg-load-more__spinner\"></span> Loading\u2026\n } @else {\n Load More\n }\n </button>\n }\n\n <!-- Inline loading indicator while appending -->\n @if (panelLoading && draggingList.length > 0) {\n <div class=\"osl-fg-item-skeleton\"></div>\n }\n }\n\n </div>\n </div>\n }\n</div><!-- /.osl-fg-wrapper -->\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-wrapper--builder{flex-direction:row;overflow:visible;align-items:stretch;gap:14px;border:none;background:transparent}.osl-fg-grid-area{flex:1 1 auto;min-width:0;display:flex;flex-direction:column;overflow:hidden;border-radius:inherit;position:relative}.osl-fg-wrapper--builder .osl-fg-grid-area{border:1px solid var(--osl-border-color, #e5e7eb);border-radius:var(--osl-border-radius, 8px);background:#fff}.osl-fg-table-drop-zone{position:absolute;inset:0;z-index:3;pointer-events:none;border-radius:inherit;transition:background .15s,border-color .15s}.osl-fg-table-drop-zone--active{border:2px dashed var(--osl-primary, #6366f1);background:#6366f108}.osl-fg-table-drop-zone--active.cdk-drop-list-receiving{background:#6366f114;border-style:solid}.osl-fg-drop-zone-badge{position:absolute;bottom:12px;left:50%;transform:translate(-50%);display:flex;align-items:center;gap:5px;padding:5px 13px;background:var(--osl-primary, #6366f1);color:#fff;border-radius:20px;font-size:11px;font-weight:600;white-space:nowrap;pointer-events:none;box-shadow:0 4px 12px #6366f159}.osl-fg-table-container{overflow-x:auto;overflow-y:auto;min-height:120px;flex:1 1 auto}.osl-fg-table-container--drop-active{outline:2px dashed var(--osl-primary, #6366f1);outline-offset:-2px}.osl-fg-table-container--drop-active .osl-fg-empty-inner{color:var(--osl-primary, #6366f1)}.osl-fg-table-container--drop-active .osl-fg-empty-inner svg{opacity:.7}.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--drag{width:28px;min-width:28px;padding:0}.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-td--drag{width:28px;min-width:28px;padding:0;text-align:center}.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-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}.osl-fg-row-handle{display:inline-flex;align-items:center;justify-content:center;width:22px;height:22px;cursor:grab;color:#d1d5db;border-radius:4px;transition:color .12s,background .12s}.osl-fg-row-handle svg{width:13px;height:13px}.osl-fg-row-handle:hover{color:#9ca3af;background:#f3f4f6}.osl-fg-row-handle:active{cursor:grabbing}.osl-fg-row-handle--disabled{cursor:not-allowed;color:#e5e7eb}.osl-fg-row-handle--disabled:hover{color:#e5e7eb;background:transparent}.osl-fg-row-handle--disabled:active{cursor:not-allowed}.osl-fg-row-placeholder td{padding:4px 6px;background:transparent;border:none}.osl-fg-drop-here{display:flex;align-items:center;justify-content:center;gap:8px;height:46px;border:2px dashed var(--osl-primary, #6366f1);border-radius:6px;background:#eef2ff;color:var(--osl-primary, #6366f1);font-size:12px;font-weight:600;letter-spacing:.03em}.osl-fg-drop-here svg{width:16px;height:16px;flex-shrink:0}.osl-fg-row-preview{display:flex;align-items:center;gap:14px;padding:9px 16px;background:#fff;border:1.5px solid var(--osl-primary, #6366f1);border-radius:8px;box-shadow:0 8px 24px #6366f12e;font-size:12px;color:#374151;max-width:420px;white-space:nowrap;overflow:hidden}.osl-fg-row-preview__cell{overflow:hidden;text-overflow:ellipsis;max-width:130px}.osl-fg-row-preview__cell+.osl-fg-row-preview__cell{padding-left:14px;border-left:1px solid #e5e7eb}.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-fg-panel{flex:0 0 auto;display:flex;flex-direction:column;border:1px solid var(--osl-border-color, #e5e7eb);border-radius:var(--osl-border-radius, 8px);background:linear-gradient(180deg,#eceafb,#e2defa);box-shadow:0 2px 6px #0f172a0f,0 8px 20px -6px #6366f129;min-height:200px;overflow:hidden}.osl-fg-panel__header{display:flex;align-items:center;justify-content:space-between;padding:11px 14px 10px;border-bottom:1px solid rgba(99,102,241,.18);background:#6366f11a;flex-shrink:0}.osl-fg-panel__title{font-size:11px;font-weight:700;color:#374151;text-transform:uppercase;letter-spacing:.06em}.osl-fg-panel__count{font-size:11px;font-weight:600;color:#9ca3af;background:#e5e7eb;border-radius:10px;padding:1px 7px;line-height:18px}.osl-fg-panel__search{position:relative;padding:8px 10px;border-bottom:1px solid rgba(99,102,241,.14);flex-shrink:0;background:#ffffff8c}.osl-fg-search-icon{position:absolute;left:20px;top:50%;transform:translateY(-50%);width:13px;height:13px;color:#d1d5db;pointer-events:none}.osl-fg-search-input{width:100%;height:30px;padding:0 26px 0 30px;border:1px solid var(--osl-border-color, #e5e7eb);border-radius:6px;background:#f9fafb;font-size:12px;font-family:inherit;color:#374151;outline:none;box-sizing:border-box;transition:border-color .15s,background .15s}.osl-fg-search-input:focus{border-color:var(--osl-primary, #6366f1);background:#fff}.osl-fg-search-input::placeholder{color:#d1d5db}.osl-fg-search-clear{position:absolute;right:16px;top:50%;transform:translateY(-50%);width:18px;height:18px;padding:0;border:none;background:transparent;color:#9ca3af;cursor:pointer;display:flex;align-items:center;justify-content:center;border-radius:3px}.osl-fg-search-clear svg{width:11px;height:11px}.osl-fg-search-clear:hover{color:#374151}.osl-fg-panel__list{flex:1 1 auto;min-height:0;overflow-y:auto;padding:8px;display:flex;flex-direction:column;gap:2px}.osl-fg-panel__empty{display:flex;flex-direction:column;align-items:center;gap:8px;padding:28px 12px;color:#c4c9d4;font-size:12px;text-align:center}.osl-fg-panel__empty svg{width:26px;height:26px;opacity:.5}.osl-fg-drag-item{display:flex;align-items:center;gap:9px;padding:8px 10px 8px 8px;border-radius:6px;cursor:grab;-webkit-user-select:none;user-select:none;transition:background .12s;background:transparent;position:relative}.osl-fg-drag-item:hover{background:#f0f4ff}.osl-fg-drag-item:hover .osl-fg-drag-item__grip{opacity:1;background:#6366f12e;color:var(--osl-primary, #6366f1)}.osl-fg-drag-item:hover .osl-fg-drag-item__arrow{opacity:.5}.osl-fg-drag-item:active{cursor:grabbing}.osl-fg-drag-item__grip{flex-shrink:0;width:20px;height:20px;opacity:.85;color:var(--osl-primary, #6366f1);background:#6366f11a;border-radius:4px;transition:opacity .15s,background .15s,color .15s;display:flex;align-items:center;justify-content:center}.osl-fg-drag-item__grip svg{width:13px;height:13px}.osl-fg-drag-item__body{flex:1 1 auto;min-width:0;display:flex;flex-direction:column;gap:1px}.osl-fg-drag-item__label{font-size:13px;font-weight:500;color:#1f2937;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;line-height:1.4}.osl-fg-drag-item__desc{font-size:11px;color:#9ca3af;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;line-height:1.3}.osl-fg-drag-item__arrow{flex-shrink:0;opacity:0;color:var(--osl-primary, #6366f1);transition:opacity .15s}.osl-fg-drag-item__arrow svg{width:13px;height:13px}.osl-fg-item-placeholder{height:36px;border:1.5px dashed #d1d5db;border-radius:6px;background:#f9fafb}.osl-fg-drag-preview{padding:8px 16px;background:var(--osl-primary, #6366f1);color:#fff;border-radius:8px;box-shadow:0 6px 18px #6366f159;font-size:12px;font-weight:600;white-space:nowrap;pointer-events:none}.osl-fg-item-skeleton{height:36px;border-radius:6px;background:#e5e7eb;animation:osl-fg-pulse 1.4s ease-in-out infinite;flex-shrink:0}.osl-fg-load-more{display:flex;align-items:center;justify-content:center;gap:6px;width:100%;height:32px;margin-top:4px;border:1px dashed var(--osl-border-color, #e5e7eb);border-radius:6px;background:transparent;color:#6b7280;font-size:12px;font-family:inherit;cursor:pointer;transition:border-color .15s,color .15s,background .15s;flex-shrink:0}.osl-fg-load-more:hover:not(:disabled){border-color:var(--osl-primary, #6366f1);color:var(--osl-primary, #6366f1);background:#f5f3ff}.osl-fg-load-more:disabled{opacity:.5;cursor:not-allowed}.osl-fg-load-more__spinner{width:12px;height:12px;border:2px solid currentColor;border-top-color:transparent;border-radius:50%;animation:osl-fg-spin .7s linear infinite;display:inline-block}@keyframes osl-fg-spin{to{transform:rotate(360deg)}}:host ::ng-deep .cdk-drag-animating{transition:transform .25s cubic-bezier(0,0,.2,1)}:host ::ng-deep .cdk-drop-list-dragging .osl-fg-drag-item:not(.cdk-drag-placeholder){transition:transform .25s cubic-bezier(0,0,.2,1)}\n"], dependencies: [{ kind: "directive", type: i1$1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i1$2.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1$2.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1$2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$2.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: i3.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { 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.CdkDragPreview, selector: "ng-template[cdkDragPreview]", inputs: ["data", "matchSize"] }, { kind: "directive", type: i3$1.CdkDragPlaceholder, selector: "ng-template[cdkDragPlaceholder]", inputs: ["data"] }, { kind: "component", type: Oslinput, selector: "osl-input", inputs: ["label", "required", "disabled", "model", "type", "placeholder", "mask", "min", "max", "minLength", "maxLength", "prefixIcon", "suffixIcon", "suffixRef", "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", "apiBody", "displayFn"], outputs: ["datasourceChange", "modelChange", "changeEv"] }, { kind: "component", type: OslFileUpload, selector: "osl-file-upload", inputs: ["label", "required", "disabled", "model", "accept", "multiple", "maxSize", "skeletonLoading", "skeletonTheme", "fileMode", "downloadFn"], 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: OslDatetimepicker, selector: "osl-datetimepicker", inputs: ["label", "required", "disabled", "model", "placeholder", "minDatetime", "maxDatetime", "format", "displayFormat", "showSeconds", "enableMeridian", "skeletonLoading", "skeletonTheme"], outputs: ["modelChange", "changeEv"] }, { kind: "component", type: OslCheckbox, selector: "osl-checkbox", inputs: ["label", "disabled", "required", "model", "indeterminate", "skeletonLoading", "skeletonTheme"], outputs: ["modelChange", "changeEv"] }] });
|
|
4600
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: OslFormGrid, deps: [{ token: i1$1.DatePipe }], target: i0.ɵɵFactoryTarget.Component });
|
|
4601
|
+
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", enableDragDrop: "enableDragDrop", draggingList: "draggingList", draggingColumns: "draggingColumns", leftPanelWidth: "leftPanelWidth", dragTitle: "dragTitle", showSearch: "showSearch", allowReorder: "allowReorder", allowRemove: "allowRemove", reorderDisabledIf: "reorderDisabledIf", panelSearchType: "panelSearchType", panelLoading: "panelLoading", panelTotalRecords: "panelTotalRecords", panelPageSize: "panelPageSize" }, outputs: { datasourceChange: "datasourceChange", rowAdd: "rowAdd", rowDelete: "rowDelete", dragStarted: "dragStarted", dragEnded: "dragEnded", dragEntered: "dragEntered", dragExited: "dragExited", itemDropped: "itemDropped", itemMoved: "itemMoved", itemRemoved: "itemRemoved", itemSelected: "itemSelected", gridChanged: "gridChanged", panelSearch: "panelSearch", panelPageChange: "panelPageChange" }, providers: [DatePipe], ngImport: i0, template: "<div class=\"osl-fg-wrapper\" [class.osl-fg-wrapper--builder]=\"enableDragDrop\">\n\n <!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\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 Available Controls panel (only in builder mode)\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\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\n\n <!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\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 Main grid area (always present)\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\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-fg-grid-area\">\n\n <!-- Panel-to-grid drop overlay: always in DOM so #gridDropZone is always resolvable.\n pointer-events: none by default; only activates visually when isDraggingFromPanel.\n CDK hit-tests by bounding rect, not pointer-events, so drops are captured regardless. -->\n <div class=\"osl-fg-table-drop-zone\"\n [class.osl-fg-table-drop-zone--active]=\"isDraggingFromPanel\"\n cdkDropList\n #gridDropZone=\"cdkDropList\"\n [cdkDropListSortingDisabled]=\"true\"\n [cdkDropListDisabled]=\"!enableDragDrop\"\n [cdkDropListData]=\"[]\"\n (cdkDropListDropped)=\"onPanelDrop($event)\">\n @if (isDraggingFromPanel) {\n <div class=\"osl-fg-drop-zone-badge\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\" width=\"13\" height=\"13\">\n <path d=\"M12 5v14M5 12l7 7 7-7\"/>\n </svg>\n Drop to Add\n </div>\n }\n </div>\n\n <div class=\"osl-fg-table-container\"\n [style.height]=\"tableHeight\"\n [class.osl-fg-table-container--drop-active]=\"isDragging && enableDragDrop\">\n\n <table class=\"osl-fg-table\">\n\n <!-- \u2500\u2500 Header \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <thead class=\"osl-fg-thead\">\n <tr>\n @if (allowReorder) {\n <th class=\"osl-fg-th osl-fg-th--drag\"></th>\n }\n @if (hasActions) {\n <th class=\"osl-fg-th osl-fg-th--actions\">\n @if (canAdd) {\n <button class=\"osl-grid-icon-btn\" (click)=\"addRow()\" [disabled]=\"loading\" title=\"Add row\">\n <mat-icon>add</mat-icon>\n </button>\n }\n </th>\n }\n @for (col of columns; track col.key) {\n <th class=\"osl-fg-th\" [style.width]=\"col.width\">\n {{ col.displayName }}\n @if (colRequired(col)) {\n <span class=\"osl-fg-th-required\">*</span>\n }\n </th>\n }\n </tr>\n </thead>\n\n <!-- \u2500\u2500 Body \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 <tbody\n cdkDropList\n #gridList=\"cdkDropList\"\n [cdkDropListData]=\"pagedData\"\n [cdkDropListDisabled]=\"!allowReorder\"\n (cdkDropListDropped)=\"onGridReorder($event)\">\n\n <!-- Loading state -->\n @if (loading) {\n @for (sk of skeletonRows; track $index) {\n <tr class=\"osl-fg-row osl-fg-row--skeleton\">\n @if (allowReorder) {\n <td class=\"osl-fg-td osl-fg-td--drag\"></td>\n }\n @if (hasActions) {\n <td class=\"osl-fg-td osl-fg-td--actions\">\n <div class=\"osl-fg-skeleton osl-fg-skeleton--sm\"></div>\n </td>\n }\n @for (col of columns; track col.key) {\n <td class=\"osl-fg-td\"><div class=\"osl-fg-skeleton\"></div></td>\n }\n </tr>\n }\n\n <!-- Empty state -->\n } @else if (pagedData.length === 0) {\n <tr>\n <td [attr.colspan]=\"columns.length + (hasActions ? 1 : 0) + (allowReorder ? 1 : 0)\"\n class=\"osl-fg-empty\">\n <div class=\"osl-fg-empty-inner\">\n @if (enableDragDrop && isDragging) {\n <svg width=\"36\" height=\"36\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\">\n <path d=\"M12 5v14M5 12l7 7 7-7\"/>\n </svg>\n <p>Drop here</p>\n } @else {\n <svg width=\"36\" height=\"36\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\">\n <rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"2\"/>\n <line x1=\"3\" y1=\"9\" x2=\"21\" y2=\"9\"/><line x1=\"9\" y1=\"9\" x2=\"9\" y2=\"21\"/>\n </svg>\n <p>No rows yet.{{ canAdd ? ' Use + to get started.' : (enableDragDrop ? ' Drag a control here.' : '') }}</p>\n }\n </div>\n </td>\n </tr>\n\n <!-- Data rows -->\n } @else {\n @for (row of pagedData; track $index; let i = $index) {\n <tr class=\"osl-fg-row\"\n cdkDrag\n [cdkDragData]=\"row\"\n [cdkDragDisabled]=\"!allowReorder || isReorderDisabled(row)\"\n (cdkDragStarted)=\"onDragStartedEvent($event, row, 'grid')\"\n (cdkDragEnded)=\"onDragEndedEvent($event, row, 'grid')\"\n (click)=\"enableDragDrop ? selectGridItem(row, i) : null\">\n\n <!-- Drag handle -->\n @if (allowReorder) {\n <td class=\"osl-fg-td osl-fg-td--drag\">\n <span class=\"osl-fg-row-handle\"\n [class.osl-fg-row-handle--disabled]=\"isReorderDisabled(row)\"\n cdkDragHandle\n [cdkDragHandleDisabled]=\"isReorderDisabled(row)\"\n [title]=\"isReorderDisabled(row) ? 'Reorder disabled' : 'Drag to reorder'\">\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <circle cx=\"9\" cy=\"5\" r=\"1.3\"/><circle cx=\"9\" cy=\"12\" r=\"1.3\"/><circle cx=\"9\" cy=\"19\" r=\"1.3\"/>\n <circle cx=\"15\" cy=\"5\" r=\"1.3\"/><circle cx=\"15\" cy=\"12\" r=\"1.3\"/><circle cx=\"15\" cy=\"19\" r=\"1.3\"/>\n </svg>\n </span>\n </td>\n }\n\n <!-- Action cell (remove / delete) -->\n @if (hasActions) {\n <td class=\"osl-fg-td osl-fg-td--actions\">\n @if (enableDragDrop && allowRemove) {\n <button class=\"osl-grid-icon-btn osl-grid-icon-btn--danger\"\n (click)=\"removeGridRow(i); $event.stopPropagation()\" title=\"Remove\">\n <mat-icon>close</mat-icon>\n </button>\n } @else if (!enableDragDrop && canDelete) {\n <button class=\"osl-grid-icon-btn osl-grid-icon-btn--danger\"\n (click)=\"deleteRow(i)\" title=\"Delete\">\n <mat-icon>delete_outline</mat-icon>\n </button>\n }\n </td>\n }\n\n <!-- Data cells -->\n @for (col of columns; track col.key) {\n <td class=\"osl-fg-td\" [style.width]=\"col.width\">\n @if (col.formElem) {\n <div class=\"osl-fg-cell-form\" [style.width]=\"col.width\">\n @switch (col.formElem.elementType) {\n\n @case ('textbox') {\n <osl-input\n [label]=\"''\"\n [required]=\"false\"\n [disabled]=\"isDisabled(col.formElem,row,i)\"\n [type]=\"col.formElem.inputType || 'text'\"\n [placeholder]=\"col.formElem.placeholder || col.displayName\"\n [mask]=\"col.formElem.mask || ''\"\n [min]=\"col.formElem.min ?? ''\"\n [max]=\"col.formElem.max ?? ''\"\n [minLength]=\"col.formElem.minLength ?? null\"\n [maxLength]=\"col.formElem.maxLength ?? null\"\n [prefixIcon]=\"col.formElem.prefixIcon || ''\"\n [suffixIcon]=\"col.formElem.suffixIcon || ''\"\n [decimalPortion]=\"col.formElem.decimalPortion ?? null\"\n [model]=\"row[col.key]\"\n (modelChange)=\"onCellChange(row, col, $event)\"\n (changeEv)=\"col.formElem.change ? col.formElem.change(row,i) : null\">\n </osl-input>\n }\n\n @case ('textarea') {\n <osl-textarea\n [label]=\"''\"\n [required]=\"false\"\n [disabled]=\"isDisabled(col.formElem,row,i)\"\n [rows]=\"col.formElem.textareaRows || 3\"\n [placeholder]=\"col.formElem.placeholder || col.displayName\"\n [maxLength]=\"col.formElem.maxLength ?? null\"\n [minLength]=\"col.formElem.minLength ?? null\"\n [characterCounter]=\"!!col.formElem.characterCounter\"\n [resize]=\"col.formElem.resize || 'none'\"\n [model]=\"row[col.key]\"\n (modelChange)=\"onCellChange(row, col, $event)\"\n (changeEv)=\"col.formElem.change ? col.formElem.change(row,i) : null\">\n </osl-textarea>\n }\n\n @case ('select') {\n <osl-select\n [label]=\"''\"\n [required]=\"false\"\n [disabled]=\"isDisabled(col.formElem,row,i)\"\n [datasource]=\"col.formElem.datasource || []\"\n [displayField]=\"col.formElem.displayField || ''\"\n [valueField]=\"col.formElem.valueField || ''\"\n [placeholder]=\"col.formElem.selectPlaceholder || col.displayName\"\n [loading]=\"isLoading(row, col.formElem)\"\n [clearable]=\"!!col.formElem.clearable\"\n [model]=\"row[col.key]\"\n (modelChange)=\"onCellChange(row, col, $event)\"\n (changeEv)=\"col.formElem.change ? onSelectChange(col, row, i, $event) : null\">\n </osl-select>\n }\n\n @case ('autocomplete') {\n <osl-autocomplete\n [label]=\"''\"\n [required]=\"false\"\n [disabled]=\"isDisabled(col.formElem,row,i)\"\n [datasource]=\"col.formElem.datasource || []\"\n [displayField]=\"col.formElem.displayField || ''\"\n [valueField]=\"col.formElem.valueField || ''\"\n [placeholder]=\"col.formElem.autocompletePlaceholder || col.displayName\"\n [loading]=\"isLoading(row, col.formElem)\"\n [searchType]=\"col.formElem.searchType ? col.formElem.searchType : 'Local'\"\n [methodName]=\"col.formElem.apiMethod || ''\"\n [configMethodName]=\"col.formElem.apiConfigMethod || ''\"\n [service]=\"col.formElem.apiService\"\n [object]=\"col.formElem.objectName ? row[col.formElem.objectName] : null\"\n [isLister]=\"!!col.formElem.isListerAutocomplete\"\n [model]=\"row[col.key]\"\n (modelChange)=\"onCellChange(row, col, $event)\"\n (changeEv)=\"col.formElem.change ? onSelectChange(col, row, i, $event) : null\">\n </osl-autocomplete>\n }\n\n @case ('radio') {\n <osl-radio\n [label]=\"''\"\n [required]=\"false\"\n [disabled]=\"isDisabled(col.formElem,row,i)\"\n [datasource]=\"col.formElem.datasource || []\"\n [displayField]=\"col.formElem.displayField || ''\"\n [valueField]=\"col.formElem.valueField || ''\"\n [inline]=\"!!col.formElem.inline\"\n [model]=\"row[col.key]\"\n (modelChange)=\"onCellChange(row, col, $event)\"\n (changeEv)=\"col.formElem.change ? col.formElem.change(row,i) : null\">\n </osl-radio>\n }\n\n @case ('checkbox') {\n <osl-checkbox\n [label]=\"''\"\n [required]=\"false\"\n [disabled]=\"isDisabled(col.formElem,row,i)\"\n [indeterminate]=\"!!col.formElem.indeterminate\"\n [model]=\"row[col.key]\"\n (modelChange)=\"onCellChange(row, col, $event)\"\n (changeEv)=\"col.formElem.change ? col.formElem.change(row,i) : null\">\n </osl-checkbox>\n }\n\n @case ('slide-toggle') {\n <osl-slide-toggle\n [label]=\"''\"\n [disabled]=\"isDisabled(col.formElem,row,i)\"\n [labelPosition]=\"col.formElem.labelPosition || 'after'\"\n [trueLabel]=\"col.formElem.trueLabel || ''\"\n [falseLabel]=\"col.formElem.falseLabel || ''\"\n [model]=\"row[col.key]\"\n (modelChange)=\"onCellChange(row, col, $event)\"\n (changeEv)=\"col.formElem.change ? col.formElem.change(row,i) : null\">\n </osl-slide-toggle>\n }\n\n @case ('datepicker') {\n <osl-datepicker\n [label]=\"''\"\n [required]=\"false\"\n [disabled]=\"isDisabled(col.formElem,row,i)\"\n [dateType]=\"col.formElem.dateType || 'date'\"\n [placeholder]=\"col.formElem.placeholder || col.displayName\"\n [minDate]=\"col.formElem.minDate || ''\"\n [maxDate]=\"col.formElem.maxDate || ''\"\n [model]=\"row[col.key]\"\n (modelChange)=\"onCellChange(row, col, $event)\"\n (changeEv)=\"col.formElem.change ? col.formElem.change(row,i) : null\">\n </osl-datepicker>\n }\n\n @case ('datetimepicker') {\n <osl-datetimepicker\n [label]=\"''\"\n [required]=\"false\"\n [disabled]=\"isDisabled(col.formElem,row,i)\"\n [placeholder]=\"col.formElem.placeholder || col.displayName\"\n [minDatetime]=\"col.formElem.minDatetime || ''\"\n [maxDatetime]=\"col.formElem.maxDatetime || ''\"\n [format]=\"col.formElem.datetimepickerFormat || 'YYYY-MM-DDTHH:mm'\"\n [showSeconds]=\"!!col.formElem.datetimepickerShowSeconds\"\n [enableMeridian]=\"!!col.formElem.datetimepickerEnableMeridian\"\n [model]=\"row[col.key]\"\n (modelChange)=\"onCellChange(row, col, $event)\"\n (changeEv)=\"col.formElem.change ? col.formElem.change(row,i) : null\">\n </osl-datetimepicker>\n }\n\n @case ('file-uploader') {\n <osl-file-upload\n [label]=\"''\"\n [required]=\"false\"\n [disabled]=\"isDisabled(col.formElem,row,i)\"\n [accept]=\"col.formElem.accept || ''\"\n [multiple]=\"!!col.formElem.multiple\"\n [maxSize]=\"col.formElem.maxFileSize || 0\"\n [model]=\"row[col.key]\"\n (modelChange)=\"onCellChange(row, col, $event)\"\n (changeEv)=\"col.formElem.change ? col.formElem.change(row,i) : null\">\n </osl-file-upload>\n }\n\n @case ('templateRef') {\n <ng-container *ngTemplateOutlet=\"col.formElem.templateRef; context: { $implicit: row, index: i }\"></ng-container>\n }\n\n }\n </div>\n } @else {\n <span class=\"osl-fg-cell-text\">{{ row[col.key] ?? '--' }}</span>\n }\n </td>\n }\n\n <!-- Row drag preview -->\n <ng-template cdkDragPreview>\n <div class=\"osl-fg-row-preview\">\n @for (col of columns; track col.key) {\n <span class=\"osl-fg-row-preview__cell\">{{ getPreviewValue(col, row) }}</span>\n }\n </div>\n </ng-template>\n\n <!-- Drop placeholder: spans full row width with \"Drop Here\" -->\n <ng-template cdkDragPlaceholder>\n <tr class=\"osl-fg-row-placeholder\">\n <td [attr.colspan]=\"columns.length + (hasActions ? 1 : 0) + (allowReorder ? 1 : 0)\">\n <div class=\"osl-fg-drop-here\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M12 5v14M5 12l7 7 7-7\"/>\n </svg>\n Drop Here\n </div>\n </td>\n </tr>\n </ng-template>\n\n </tr>\n }\n }\n\n </tbody>\n\n <!-- \u2500\u2500 Footer \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 (footerColumns && footerColumns.length > 0 && datasource && datasource.length > 0) {\n <tfoot class=\"osl-fg-tfoot\">\n <tr class=\"osl-fg-footer-row\">\n @for (fcol of footerColumns; track $index) {\n <td\n [class]=\"'osl-fg-footer-td ' + (fcol.class || '')\"\n [attr.colspan]=\"fcol.colspan || 1\">\n {{ fcol.displayFn ? fcol.displayFn(datasource) : (fcol.display || '') }}\n </td>\n }\n </tr>\n </tfoot>\n }\n\n </table>\n </div>\n\n <!-- \u2500\u2500 Pagination (unchanged) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 (isPaginated) {\n <div class=\"osl-fg-pagination\">\n\n <span class=\"osl-fg-pagination__info\">\n @if (loading) {\n Loading...\n } @else if (_total > 0) {\n {{ startRecord }}\u2013{{ endRecord }} of {{ _total }} rows\n } @else {\n No rows\n }\n </span>\n\n <div class=\"osl-fg-pagination__controls\">\n <button class=\"osl-fg-page-btn osl-fg-page-btn--nav\" (click)=\"goToPage(1)\" [disabled]=\"currentPage === 1 || loading\">\u00AB</button>\n <button class=\"osl-fg-page-btn osl-fg-page-btn--nav\" (click)=\"goToPage(currentPage - 1)\" [disabled]=\"currentPage === 1 || loading\">\u2039 Prev</button>\n\n @for (page of pageNumbers; track $index) {\n @if (page === -1) {\n <span class=\"osl-fg-page-ellipsis\">\u2026</span>\n } @else {\n <button\n class=\"osl-fg-page-btn osl-fg-page-btn--num\"\n [class.osl-fg-page-btn--active]=\"page === currentPage\"\n [disabled]=\"loading\"\n (click)=\"goToPage(page)\">\n {{ page }}\n </button>\n }\n }\n\n <button class=\"osl-fg-page-btn osl-fg-page-btn--nav\" (click)=\"goToPage(currentPage + 1)\" [disabled]=\"currentPage === totalPages || loading\">Next \u203A</button>\n <button class=\"osl-fg-page-btn osl-fg-page-btn--nav\" (click)=\"goToPage(totalPages)\" [disabled]=\"currentPage === totalPages || loading\">\u00BB</button>\n </div>\n\n <div class=\"osl-fg-pagination__size\">\n <select class=\"osl-fg-page-size\" [ngModel]=\"pageSize\" (ngModelChange)=\"onPageSizeChange($event)\" [disabled]=\"loading\">\n @for (opt of pageSizeOptions; track opt) {\n <option [value]=\"opt\">{{ opt }} per page</option>\n }\n </select>\n </div>\n\n </div>\n }\n\n </div><!-- /.osl-fg-grid-area -->\n @if (enableDragDrop) {\n <div class=\"osl-fg-panel\" [style.width.px]=\"leftPanelWidth\" [style.height]=\"tableHeight\">\n\n <!-- Panel header -->\n <div class=\"osl-fg-panel__header\">\n <span class=\"osl-fg-panel__title\">{{ dragTitle }}</span>\n @if (panelSearchType === 'Api' && panelTotalRecords > 0) {\n <span class=\"osl-fg-panel__count\">{{ draggingList.length }}/{{ panelTotalRecords }}</span>\n } @else if (filteredDraggingList.length > 0) {\n <span class=\"osl-fg-panel__count\">{{ filteredDraggingList.length }}</span>\n }\n </div>\n\n <!-- Search box -->\n @if (showSearch) {\n <div class=\"osl-fg-panel__search\">\n <svg class=\"osl-fg-search-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <circle cx=\"11\" cy=\"11\" r=\"8\"/><line x1=\"21\" y1=\"21\" x2=\"16.65\" y2=\"16.65\"/>\n </svg>\n <input\n class=\"osl-fg-search-input\"\n type=\"text\"\n [ngModel]=\"dragSearchQuery\"\n (ngModelChange)=\"onPanelSearchChange($event)\"\n placeholder=\"Search\u2026\"\n autocomplete=\"off\" />\n @if (dragSearchQuery) {\n <button class=\"osl-fg-search-clear\" (click)=\"clearPanelSearch()\" title=\"Clear\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"/><line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"/>\n </svg>\n </button>\n }\n </div>\n }\n\n <!-- Item list -->\n <div class=\"osl-fg-panel__list\"\n cdkDropList\n #availableList=\"cdkDropList\"\n [cdkDropListData]=\"filteredDraggingList\"\n [cdkDropListConnectedTo]=\"[gridDropZone]\"\n [cdkDropListSortingDisabled]=\"true\"\n (cdkDropListEntered)=\"onDragEnteredEvent($event, 'available')\"\n (cdkDropListExited)=\"onDragExitedEvent($event, 'available')\">\n\n <!-- Loading skeleton -->\n @if (panelLoading && draggingList.length === 0) {\n @for (sk of [1,2,3,4,5]; track sk) {\n <div class=\"osl-fg-item-skeleton\"></div>\n }\n\n } @else if (filteredDraggingList.length === 0) {\n <div class=\"osl-fg-panel__empty\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\">\n <circle cx=\"11\" cy=\"11\" r=\"8\"/><line x1=\"21\" y1=\"21\" x2=\"16.65\" y2=\"16.65\"/>\n </svg>\n <span>{{ dragSearchQuery ? 'No results found' : 'No results available' }}</span>\n </div>\n\n } @else {\n @for (item of filteredDraggingList; track item.id) {\n <div class=\"osl-fg-drag-item\"\n cdkDrag\n [cdkDragData]=\"item\"\n (cdkDragStarted)=\"onDragStartedEvent($event, item, 'available')\"\n (cdkDragEnded)=\"onDragEndedEvent($event, item, 'available')\">\n\n <!-- Grip handle -->\n <span class=\"osl-fg-drag-item__grip\">\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <circle cx=\"9\" cy=\"5\" r=\"1.4\"/><circle cx=\"9\" cy=\"12\" r=\"1.4\"/><circle cx=\"9\" cy=\"19\" r=\"1.4\"/>\n <circle cx=\"15\" cy=\"5\" r=\"1.4\"/><circle cx=\"15\" cy=\"12\" r=\"1.4\"/><circle cx=\"15\" cy=\"19\" r=\"1.4\"/>\n </svg>\n </span>\n\n <!-- Label + description -->\n <div class=\"osl-fg-drag-item__body\">\n <span class=\"osl-fg-drag-item__label\">{{ item.label }}</span>\n @if (item.description) {\n <span class=\"osl-fg-drag-item__desc\">{{ item.description }}</span>\n }\n </div>\n\n <!-- Drag-into-grid hint -->\n <span class=\"osl-fg-drag-item__arrow\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <line x1=\"5\" y1=\"12\" x2=\"19\" y2=\"12\"/><polyline points=\"12 5 19 12 12 19\"/>\n </svg>\n </span>\n\n <!-- Placeholder shown in panel while card is dragged -->\n <div *cdkDragPlaceholder class=\"osl-fg-item-placeholder\"></div>\n\n <!-- Preview under the cursor while dragging -->\n <ng-template cdkDragPreview>\n <div class=\"osl-fg-drag-preview\">{{ item.label }}</div>\n </ng-template>\n\n </div>\n }\n\n <!-- Load More (API mode) -->\n @if (panelHasMore) {\n <button class=\"osl-fg-load-more\" (click)=\"loadMorePanelItems()\" [disabled]=\"panelLoading\">\n @if (panelLoading) {\n <span class=\"osl-fg-load-more__spinner\"></span> Loading\u2026\n } @else {\n Load More\n }\n </button>\n }\n\n <!-- Inline loading indicator while appending -->\n @if (panelLoading && draggingList.length > 0) {\n <div class=\"osl-fg-item-skeleton\"></div>\n }\n }\n\n </div>\n </div>\n }\n</div><!-- /.osl-fg-wrapper -->\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-wrapper--builder{flex-direction:row;overflow:visible;align-items:stretch;gap:14px;border:none;background:transparent}.osl-fg-grid-area{flex:1 1 auto;min-width:0;display:flex;flex-direction:column;overflow:hidden;border-radius:inherit;position:relative}.osl-fg-wrapper--builder .osl-fg-grid-area{border:1px solid var(--osl-border-color, #e5e7eb);border-radius:var(--osl-border-radius, 8px);background:#fff}.osl-fg-table-drop-zone{position:absolute;inset:0;z-index:3;pointer-events:none;border-radius:inherit;transition:background .15s,border-color .15s}.osl-fg-table-drop-zone--active{border:2px dashed var(--osl-primary, #6366f1);background:#6366f108}.osl-fg-table-drop-zone--active.cdk-drop-list-receiving{background:#6366f114;border-style:solid}.osl-fg-drop-zone-badge{position:absolute;bottom:12px;left:50%;transform:translate(-50%);display:flex;align-items:center;gap:5px;padding:5px 13px;background:var(--osl-primary, #6366f1);color:#fff;border-radius:20px;font-size:11px;font-weight:600;white-space:nowrap;pointer-events:none;box-shadow:0 4px 12px #6366f159}.osl-fg-table-container{overflow-x:auto;overflow-y:auto;min-height:120px;flex:1 1 auto}.osl-fg-table-container--drop-active{outline:2px dashed var(--osl-primary, #6366f1);outline-offset:-2px}.osl-fg-table-container--drop-active .osl-fg-empty-inner{color:var(--osl-primary, #6366f1)}.osl-fg-table-container--drop-active .osl-fg-empty-inner svg{opacity:.7}.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--drag{width:28px;min-width:28px;padding:0}.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-td--drag{width:28px;min-width:28px;padding:0;text-align:center}.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-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}.osl-fg-row-handle{display:inline-flex;align-items:center;justify-content:center;width:22px;height:22px;cursor:grab;color:#d1d5db;border-radius:4px;transition:color .12s,background .12s}.osl-fg-row-handle svg{width:13px;height:13px}.osl-fg-row-handle:hover{color:#9ca3af;background:#f3f4f6}.osl-fg-row-handle:active{cursor:grabbing}.osl-fg-row-handle--disabled{cursor:not-allowed;color:#e5e7eb}.osl-fg-row-handle--disabled:hover{color:#e5e7eb;background:transparent}.osl-fg-row-handle--disabled:active{cursor:not-allowed}.osl-fg-row-placeholder td{padding:4px 6px;background:transparent;border:none}.osl-fg-drop-here{display:flex;align-items:center;justify-content:center;gap:8px;height:46px;border:2px dashed var(--osl-primary, #6366f1);border-radius:6px;background:#eef2ff;color:var(--osl-primary, #6366f1);font-size:12px;font-weight:600;letter-spacing:.03em}.osl-fg-drop-here svg{width:16px;height:16px;flex-shrink:0}.osl-fg-row-preview{display:flex;align-items:center;gap:14px;padding:9px 16px;background:#fff;border:1.5px solid var(--osl-primary, #6366f1);border-radius:8px;box-shadow:0 8px 24px #6366f12e;font-size:12px;color:#374151;max-width:420px;white-space:nowrap;overflow:hidden}.osl-fg-row-preview__cell{overflow:hidden;text-overflow:ellipsis;max-width:130px}.osl-fg-row-preview__cell+.osl-fg-row-preview__cell{padding-left:14px;border-left:1px solid #e5e7eb}.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-fg-panel{flex:0 0 auto;display:flex;flex-direction:column;border:1px solid var(--osl-border-color, #e5e7eb);border-radius:var(--osl-border-radius, 8px);background:linear-gradient(180deg,#eceafb,#e2defa);box-shadow:0 2px 6px #0f172a0f,0 8px 20px -6px #6366f129;min-height:200px;overflow:hidden}.osl-fg-panel__header{display:flex;align-items:center;justify-content:space-between;padding:11px 14px 10px;border-bottom:1px solid rgba(99,102,241,.18);background:#6366f11a;flex-shrink:0}.osl-fg-panel__title{font-size:11px;font-weight:700;color:#374151;text-transform:uppercase;letter-spacing:.06em}.osl-fg-panel__count{font-size:11px;font-weight:600;color:#9ca3af;background:#e5e7eb;border-radius:10px;padding:1px 7px;line-height:18px}.osl-fg-panel__search{position:relative;padding:8px 10px;border-bottom:1px solid rgba(99,102,241,.14);flex-shrink:0;background:#ffffff8c}.osl-fg-search-icon{position:absolute;left:20px;top:50%;transform:translateY(-50%);width:13px;height:13px;color:#d1d5db;pointer-events:none}.osl-fg-search-input{width:100%;height:30px;padding:0 26px 0 30px;border:1px solid var(--osl-border-color, #e5e7eb);border-radius:6px;background:#f9fafb;font-size:12px;font-family:inherit;color:#374151;outline:none;box-sizing:border-box;transition:border-color .15s,background .15s}.osl-fg-search-input:focus{border-color:var(--osl-primary, #6366f1);background:#fff}.osl-fg-search-input::placeholder{color:#d1d5db}.osl-fg-search-clear{position:absolute;right:16px;top:50%;transform:translateY(-50%);width:18px;height:18px;padding:0;border:none;background:transparent;color:#9ca3af;cursor:pointer;display:flex;align-items:center;justify-content:center;border-radius:3px}.osl-fg-search-clear svg{width:11px;height:11px}.osl-fg-search-clear:hover{color:#374151}.osl-fg-panel__list{flex:1 1 auto;min-height:0;overflow-y:auto;padding:8px;display:flex;flex-direction:column;gap:2px}.osl-fg-panel__empty{display:flex;flex-direction:column;align-items:center;gap:8px;padding:28px 12px;color:#c4c9d4;font-size:12px;text-align:center}.osl-fg-panel__empty svg{width:26px;height:26px;opacity:.5}.osl-fg-drag-item{display:flex;align-items:center;gap:9px;padding:8px 10px 8px 8px;border-radius:6px;cursor:grab;-webkit-user-select:none;user-select:none;transition:background .12s;background:transparent;position:relative}.osl-fg-drag-item:hover{background:#f0f4ff}.osl-fg-drag-item:hover .osl-fg-drag-item__grip{opacity:1;background:#6366f12e;color:var(--osl-primary, #6366f1)}.osl-fg-drag-item:hover .osl-fg-drag-item__arrow{opacity:.5}.osl-fg-drag-item:active{cursor:grabbing}.osl-fg-drag-item__grip{flex-shrink:0;width:20px;height:20px;opacity:.85;color:var(--osl-primary, #6366f1);background:#6366f11a;border-radius:4px;transition:opacity .15s,background .15s,color .15s;display:flex;align-items:center;justify-content:center}.osl-fg-drag-item__grip svg{width:13px;height:13px}.osl-fg-drag-item__body{flex:1 1 auto;min-width:0;display:flex;flex-direction:column;gap:1px}.osl-fg-drag-item__label{font-size:13px;font-weight:500;color:#1f2937;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;line-height:1.4}.osl-fg-drag-item__desc{font-size:11px;color:#9ca3af;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;line-height:1.3}.osl-fg-drag-item__arrow{flex-shrink:0;opacity:0;color:var(--osl-primary, #6366f1);transition:opacity .15s}.osl-fg-drag-item__arrow svg{width:13px;height:13px}.osl-fg-item-placeholder{height:36px;border:1.5px dashed #d1d5db;border-radius:6px;background:#f9fafb}.osl-fg-drag-preview{padding:8px 16px;background:var(--osl-primary, #6366f1);color:#fff;border-radius:8px;box-shadow:0 6px 18px #6366f159;font-size:12px;font-weight:600;white-space:nowrap;pointer-events:none}.osl-fg-item-skeleton{height:36px;border-radius:6px;background:#e5e7eb;animation:osl-fg-pulse 1.4s ease-in-out infinite;flex-shrink:0}.osl-fg-load-more{display:flex;align-items:center;justify-content:center;gap:6px;width:100%;height:32px;margin-top:4px;border:1px dashed var(--osl-border-color, #e5e7eb);border-radius:6px;background:transparent;color:#6b7280;font-size:12px;font-family:inherit;cursor:pointer;transition:border-color .15s,color .15s,background .15s;flex-shrink:0}.osl-fg-load-more:hover:not(:disabled){border-color:var(--osl-primary, #6366f1);color:var(--osl-primary, #6366f1);background:#f5f3ff}.osl-fg-load-more:disabled{opacity:.5;cursor:not-allowed}.osl-fg-load-more__spinner{width:12px;height:12px;border:2px solid currentColor;border-top-color:transparent;border-radius:50%;animation:osl-fg-spin .7s linear infinite;display:inline-block}@keyframes osl-fg-spin{to{transform:rotate(360deg)}}:host ::ng-deep .cdk-drag-animating{transition:transform .25s cubic-bezier(0,0,.2,1)}:host ::ng-deep .cdk-drop-list-dragging .osl-fg-drag-item:not(.cdk-drag-placeholder){transition:transform .25s cubic-bezier(0,0,.2,1)}\n"], dependencies: [{ kind: "directive", type: i1$1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i1$2.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1$2.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1$2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$2.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: i3.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { 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.CdkDragPreview, selector: "ng-template[cdkDragPreview]", inputs: ["data", "matchSize"] }, { kind: "directive", type: i3$1.CdkDragPlaceholder, selector: "ng-template[cdkDragPlaceholder]", inputs: ["data"] }, { kind: "component", type: Oslinput, selector: "osl-input", inputs: ["label", "required", "disabled", "model", "type", "placeholder", "mask", "min", "max", "minLength", "maxLength", "prefixIcon", "suffixIcon", "suffixRef", "skeletonLoading", "skeletonTheme", "onlyChars", "isCapitalize", "decimalPortion", "allowNegative"], 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", "apiBody", "displayFn"], outputs: ["datasourceChange", "modelChange", "changeEv"] }, { kind: "component", type: OslFileUpload, selector: "osl-file-upload", inputs: ["label", "required", "disabled", "model", "accept", "multiple", "maxSize", "skeletonLoading", "skeletonTheme", "fileMode", "downloadFn"], 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: OslDatetimepicker, selector: "osl-datetimepicker", inputs: ["label", "required", "disabled", "model", "placeholder", "minDatetime", "maxDatetime", "format", "displayFormat", "showSeconds", "enableMeridian", "skeletonLoading", "skeletonTheme"], outputs: ["modelChange", "changeEv"] }, { kind: "component", type: OslCheckbox, selector: "osl-checkbox", inputs: ["label", "disabled", "required", "model", "indeterminate", "skeletonLoading", "skeletonTheme"], outputs: ["modelChange", "changeEv"] }] });
|
|
4528
4602
|
}
|
|
4529
4603
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: OslFormGrid, decorators: [{
|
|
4530
4604
|
type: Component,
|
|
4531
|
-
args: [{ selector: 'osl-form-grid', standalone: false, template: "<div class=\"osl-fg-wrapper\" [class.osl-fg-wrapper--builder]=\"enableDragDrop\">\n\n <!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\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 Available Controls panel (only in builder mode)\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\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\n\n <!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\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 Main grid area (always present)\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\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-fg-grid-area\">\n\n <!-- Panel-to-grid drop overlay: always in DOM so #gridDropZone is always resolvable.\n pointer-events: none by default; only activates visually when isDraggingFromPanel.\n CDK hit-tests by bounding rect, not pointer-events, so drops are captured regardless. -->\n <div class=\"osl-fg-table-drop-zone\"\n [class.osl-fg-table-drop-zone--active]=\"isDraggingFromPanel\"\n cdkDropList\n #gridDropZone=\"cdkDropList\"\n [cdkDropListSortingDisabled]=\"true\"\n [cdkDropListDisabled]=\"!enableDragDrop\"\n [cdkDropListData]=\"[]\"\n (cdkDropListDropped)=\"onPanelDrop($event)\">\n @if (isDraggingFromPanel) {\n <div class=\"osl-fg-drop-zone-badge\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\" width=\"13\" height=\"13\">\n <path d=\"M12 5v14M5 12l7 7 7-7\"/>\n </svg>\n Drop to Add\n </div>\n }\n </div>\n\n <div class=\"osl-fg-table-container\"\n [style.height]=\"tableHeight\"\n [class.osl-fg-table-container--drop-active]=\"isDragging && enableDragDrop\">\n\n <table class=\"osl-fg-table\">\n\n <!-- \u2500\u2500 Header \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <thead class=\"osl-fg-thead\">\n <tr>\n @if (allowReorder) {\n <th class=\"osl-fg-th osl-fg-th--drag\"></th>\n }\n @if (hasActions) {\n <th class=\"osl-fg-th osl-fg-th--actions\">\n @if (canAdd) {\n <button class=\"osl-grid-icon-btn\" (click)=\"addRow()\" [disabled]=\"loading\" title=\"Add row\">\n <mat-icon>add</mat-icon>\n </button>\n }\n </th>\n }\n @for (col of columns; track col.key) {\n <th class=\"osl-fg-th\" [style.width]=\"col.width\">\n {{ col.displayName }}\n @if (colRequired(col)) {\n <span class=\"osl-fg-th-required\">*</span>\n }\n </th>\n }\n </tr>\n </thead>\n\n <!-- \u2500\u2500 Body \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 <tbody\n cdkDropList\n #gridList=\"cdkDropList\"\n [cdkDropListData]=\"pagedData\"\n [cdkDropListDisabled]=\"!allowReorder\"\n (cdkDropListDropped)=\"onGridReorder($event)\">\n\n <!-- Loading state -->\n @if (loading) {\n @for (sk of skeletonRows; track $index) {\n <tr class=\"osl-fg-row osl-fg-row--skeleton\">\n @if (allowReorder) {\n <td class=\"osl-fg-td osl-fg-td--drag\"></td>\n }\n @if (hasActions) {\n <td class=\"osl-fg-td osl-fg-td--actions\">\n <div class=\"osl-fg-skeleton osl-fg-skeleton--sm\"></div>\n </td>\n }\n @for (col of columns; track col.key) {\n <td class=\"osl-fg-td\"><div class=\"osl-fg-skeleton\"></div></td>\n }\n </tr>\n }\n\n <!-- Empty state -->\n } @else if (pagedData.length === 0) {\n <tr>\n <td [attr.colspan]=\"columns.length + (hasActions ? 1 : 0) + (allowReorder ? 1 : 0)\"\n class=\"osl-fg-empty\">\n <div class=\"osl-fg-empty-inner\">\n @if (enableDragDrop && isDragging) {\n <svg width=\"36\" height=\"36\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\">\n <path d=\"M12 5v14M5 12l7 7 7-7\"/>\n </svg>\n <p>Drop here</p>\n } @else {\n <svg width=\"36\" height=\"36\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\">\n <rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"2\"/>\n <line x1=\"3\" y1=\"9\" x2=\"21\" y2=\"9\"/><line x1=\"9\" y1=\"9\" x2=\"9\" y2=\"21\"/>\n </svg>\n <p>No rows yet.{{ canAdd ? ' Use + to get started.' : (enableDragDrop ? ' Drag a control here.' : '') }}</p>\n }\n </div>\n </td>\n </tr>\n\n <!-- Data rows -->\n } @else {\n @for (row of pagedData; track $index; let i = $index) {\n <tr class=\"osl-fg-row\"\n cdkDrag\n [cdkDragData]=\"row\"\n [cdkDragDisabled]=\"!allowReorder || isReorderDisabled(row)\"\n (cdkDragStarted)=\"onDragStartedEvent($event, row, 'grid')\"\n (cdkDragEnded)=\"onDragEndedEvent($event, row, 'grid')\"\n (click)=\"enableDragDrop ? selectGridItem(row, i) : null\">\n\n <!-- Drag handle -->\n @if (allowReorder) {\n <td class=\"osl-fg-td osl-fg-td--drag\">\n <span class=\"osl-fg-row-handle\"\n [class.osl-fg-row-handle--disabled]=\"isReorderDisabled(row)\"\n cdkDragHandle\n [cdkDragHandleDisabled]=\"isReorderDisabled(row)\"\n [title]=\"isReorderDisabled(row) ? 'Reorder disabled' : 'Drag to reorder'\">\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <circle cx=\"9\" cy=\"5\" r=\"1.3\"/><circle cx=\"9\" cy=\"12\" r=\"1.3\"/><circle cx=\"9\" cy=\"19\" r=\"1.3\"/>\n <circle cx=\"15\" cy=\"5\" r=\"1.3\"/><circle cx=\"15\" cy=\"12\" r=\"1.3\"/><circle cx=\"15\" cy=\"19\" r=\"1.3\"/>\n </svg>\n </span>\n </td>\n }\n\n <!-- Action cell (remove / delete) -->\n @if (hasActions) {\n <td class=\"osl-fg-td osl-fg-td--actions\">\n @if (enableDragDrop && allowRemove) {\n <button class=\"osl-grid-icon-btn osl-grid-icon-btn--danger\"\n (click)=\"removeGridRow(i); $event.stopPropagation()\" title=\"Remove\">\n <mat-icon>close</mat-icon>\n </button>\n } @else if (!enableDragDrop && canDelete) {\n <button class=\"osl-grid-icon-btn osl-grid-icon-btn--danger\"\n (click)=\"deleteRow(i)\" title=\"Delete\">\n <mat-icon>delete_outline</mat-icon>\n </button>\n }\n </td>\n }\n\n <!-- Data cells -->\n @for (col of columns; track col.key) {\n <td class=\"osl-fg-td\" [style.width]=\"col.width\">\n @if (col.formElem) {\n <div class=\"osl-fg-cell-form\" [style.width]=\"col.width\">\n @switch (col.formElem.elementType) {\n\n @case ('textbox') {\n <osl-input\n [label]=\"''\"\n [required]=\"false\"\n [disabled]=\"isDisabled(col.formElem,row,i)\"\n [type]=\"col.formElem.inputType || 'text'\"\n [placeholder]=\"col.formElem.placeholder || col.displayName\"\n [mask]=\"col.formElem.mask || ''\"\n [min]=\"col.formElem.min ?? ''\"\n [max]=\"col.formElem.max ?? ''\"\n [minLength]=\"col.formElem.minLength ?? null\"\n [maxLength]=\"col.formElem.maxLength ?? null\"\n [prefixIcon]=\"col.formElem.prefixIcon || ''\"\n [suffixIcon]=\"col.formElem.suffixIcon || ''\"\n [decimalPortion]=\"col.formElem.decimalPortion ?? null\"\n [model]=\"row[col.key]\"\n (modelChange)=\"onCellChange(row, col, $event)\"\n (changeEv)=\"col.formElem.change ? col.formElem.change(row,i) : null\">\n </osl-input>\n }\n\n @case ('textarea') {\n <osl-textarea\n [label]=\"''\"\n [required]=\"false\"\n [disabled]=\"isDisabled(col.formElem,row,i)\"\n [rows]=\"col.formElem.textareaRows || 3\"\n [placeholder]=\"col.formElem.placeholder || col.displayName\"\n [maxLength]=\"col.formElem.maxLength ?? null\"\n [minLength]=\"col.formElem.minLength ?? null\"\n [characterCounter]=\"!!col.formElem.characterCounter\"\n [resize]=\"col.formElem.resize || 'none'\"\n [model]=\"row[col.key]\"\n (modelChange)=\"onCellChange(row, col, $event)\"\n (changeEv)=\"col.formElem.change ? col.formElem.change(row,i) : null\">\n </osl-textarea>\n }\n\n @case ('select') {\n <osl-select\n [label]=\"''\"\n [required]=\"false\"\n [disabled]=\"isDisabled(col.formElem,row,i)\"\n [datasource]=\"col.formElem.datasource || []\"\n [displayField]=\"col.formElem.displayField || ''\"\n [valueField]=\"col.formElem.valueField || ''\"\n [placeholder]=\"col.formElem.selectPlaceholder || col.displayName\"\n [loading]=\"isLoading(row, col.formElem)\"\n [clearable]=\"!!col.formElem.clearable\"\n [model]=\"row[col.key]\"\n (modelChange)=\"onCellChange(row, col, $event)\"\n (changeEv)=\"col.formElem.change ? onSelectChange(col, row, i, $event) : null\">\n </osl-select>\n }\n\n @case ('autocomplete') {\n <osl-autocomplete\n [label]=\"''\"\n [required]=\"false\"\n [disabled]=\"isDisabled(col.formElem,row,i)\"\n [datasource]=\"col.formElem.datasource || []\"\n [displayField]=\"col.formElem.displayField || ''\"\n [valueField]=\"col.formElem.valueField || ''\"\n [placeholder]=\"col.formElem.autocompletePlaceholder || col.displayName\"\n [loading]=\"isLoading(row, col.formElem)\"\n [searchType]=\"col.formElem.searchType ? col.formElem.searchType : 'Local'\"\n [methodName]=\"col.formElem.apiMethod || ''\"\n [configMethodName]=\"col.formElem.apiConfigMethod || ''\"\n [service]=\"col.formElem.apiService\"\n [object]=\"col.formElem.objectName ? row[col.formElem.objectName] : null\"\n [isLister]=\"!!col.formElem.isListerAutocomplete\"\n [model]=\"row[col.key]\"\n (modelChange)=\"onCellChange(row, col, $event)\"\n (changeEv)=\"col.formElem.change ? onSelectChange(col, row, i, $event) : null\">\n </osl-autocomplete>\n }\n\n @case ('radio') {\n <osl-radio\n [label]=\"''\"\n [required]=\"false\"\n [disabled]=\"isDisabled(col.formElem,row,i)\"\n [datasource]=\"col.formElem.datasource || []\"\n [displayField]=\"col.formElem.displayField || ''\"\n [valueField]=\"col.formElem.valueField || ''\"\n [inline]=\"!!col.formElem.inline\"\n [model]=\"row[col.key]\"\n (modelChange)=\"onCellChange(row, col, $event)\"\n (changeEv)=\"col.formElem.change ? col.formElem.change(row,i) : null\">\n </osl-radio>\n }\n\n @case ('checkbox') {\n <osl-checkbox\n [label]=\"''\"\n [required]=\"false\"\n [disabled]=\"isDisabled(col.formElem,row,i)\"\n [indeterminate]=\"!!col.formElem.indeterminate\"\n [model]=\"row[col.key]\"\n (modelChange)=\"onCellChange(row, col, $event)\"\n (changeEv)=\"col.formElem.change ? col.formElem.change(row,i) : null\">\n </osl-checkbox>\n }\n\n @case ('slide-toggle') {\n <osl-slide-toggle\n [label]=\"''\"\n [disabled]=\"isDisabled(col.formElem,row,i)\"\n [labelPosition]=\"col.formElem.labelPosition || 'after'\"\n [trueLabel]=\"col.formElem.trueLabel || ''\"\n [falseLabel]=\"col.formElem.falseLabel || ''\"\n [model]=\"row[col.key]\"\n (modelChange)=\"onCellChange(row, col, $event)\"\n (changeEv)=\"col.formElem.change ? col.formElem.change(row,i) : null\">\n </osl-slide-toggle>\n }\n\n @case ('datepicker') {\n <osl-datepicker\n [label]=\"''\"\n [required]=\"false\"\n [disabled]=\"isDisabled(col.formElem,row,i)\"\n [dateType]=\"col.formElem.dateType || 'date'\"\n [placeholder]=\"col.formElem.placeholder || col.displayName\"\n [minDate]=\"col.formElem.minDate || ''\"\n [maxDate]=\"col.formElem.maxDate || ''\"\n [model]=\"row[col.key]\"\n (modelChange)=\"onCellChange(row, col, $event)\"\n (changeEv)=\"col.formElem.change ? col.formElem.change(row,i) : null\">\n </osl-datepicker>\n }\n\n @case ('datetimepicker') {\n <osl-datetimepicker\n [label]=\"''\"\n [required]=\"false\"\n [disabled]=\"isDisabled(col.formElem,row,i)\"\n [placeholder]=\"col.formElem.placeholder || col.displayName\"\n [minDatetime]=\"col.formElem.minDatetime || ''\"\n [maxDatetime]=\"col.formElem.maxDatetime || ''\"\n [format]=\"col.formElem.datetimepickerFormat || 'YYYY-MM-DDTHH:mm'\"\n [showSeconds]=\"!!col.formElem.datetimepickerShowSeconds\"\n [enableMeridian]=\"!!col.formElem.datetimepickerEnableMeridian\"\n [model]=\"row[col.key]\"\n (modelChange)=\"onCellChange(row, col, $event)\"\n (changeEv)=\"col.formElem.change ? col.formElem.change(row,i) : null\">\n </osl-datetimepicker>\n }\n\n @case ('file-uploader') {\n <osl-file-upload\n [label]=\"''\"\n [required]=\"false\"\n [disabled]=\"isDisabled(col.formElem,row,i)\"\n [accept]=\"col.formElem.accept || ''\"\n [multiple]=\"!!col.formElem.multiple\"\n [maxSize]=\"col.formElem.maxFileSize || 0\"\n [model]=\"row[col.key]\"\n (modelChange)=\"onCellChange(row, col, $event)\"\n (changeEv)=\"col.formElem.change ? col.formElem.change(row,i) : null\">\n </osl-file-upload>\n }\n\n @case ('templateRef') {\n <ng-container *ngTemplateOutlet=\"col.formElem.templateRef; context: { $implicit: row, index: i }\"></ng-container>\n }\n\n }\n </div>\n } @else {\n <span class=\"osl-fg-cell-text\">{{ row[col.key] ?? '--' }}</span>\n }\n </td>\n }\n\n <!-- Row drag preview -->\n <ng-template cdkDragPreview>\n <div class=\"osl-fg-row-preview\">\n @for (col of columns; track col.key) {\n <span class=\"osl-fg-row-preview__cell\">{{ row[col.key] ?? '\u2014' }}</span>\n }\n </div>\n </ng-template>\n\n <!-- Drop placeholder: spans full row width with \"Drop Here\" -->\n <ng-template cdkDragPlaceholder>\n <tr class=\"osl-fg-row-placeholder\">\n <td [attr.colspan]=\"columns.length + (hasActions ? 1 : 0) + (allowReorder ? 1 : 0)\">\n <div class=\"osl-fg-drop-here\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M12 5v14M5 12l7 7 7-7\"/>\n </svg>\n Drop Here\n </div>\n </td>\n </tr>\n </ng-template>\n\n </tr>\n }\n }\n\n </tbody>\n\n <!-- \u2500\u2500 Footer \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 (footerColumns && footerColumns.length > 0 && datasource && datasource.length > 0) {\n <tfoot class=\"osl-fg-tfoot\">\n <tr class=\"osl-fg-footer-row\">\n @for (fcol of footerColumns; track $index) {\n <td\n [class]=\"'osl-fg-footer-td ' + (fcol.class || '')\"\n [attr.colspan]=\"fcol.colspan || 1\">\n {{ fcol.displayFn ? fcol.displayFn(datasource) : (fcol.display || '') }}\n </td>\n }\n </tr>\n </tfoot>\n }\n\n </table>\n </div>\n\n <!-- \u2500\u2500 Pagination (unchanged) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 (isPaginated) {\n <div class=\"osl-fg-pagination\">\n\n <span class=\"osl-fg-pagination__info\">\n @if (loading) {\n Loading...\n } @else if (_total > 0) {\n {{ startRecord }}\u2013{{ endRecord }} of {{ _total }} rows\n } @else {\n No rows\n }\n </span>\n\n <div class=\"osl-fg-pagination__controls\">\n <button class=\"osl-fg-page-btn osl-fg-page-btn--nav\" (click)=\"goToPage(1)\" [disabled]=\"currentPage === 1 || loading\">\u00AB</button>\n <button class=\"osl-fg-page-btn osl-fg-page-btn--nav\" (click)=\"goToPage(currentPage - 1)\" [disabled]=\"currentPage === 1 || loading\">\u2039 Prev</button>\n\n @for (page of pageNumbers; track $index) {\n @if (page === -1) {\n <span class=\"osl-fg-page-ellipsis\">\u2026</span>\n } @else {\n <button\n class=\"osl-fg-page-btn osl-fg-page-btn--num\"\n [class.osl-fg-page-btn--active]=\"page === currentPage\"\n [disabled]=\"loading\"\n (click)=\"goToPage(page)\">\n {{ page }}\n </button>\n }\n }\n\n <button class=\"osl-fg-page-btn osl-fg-page-btn--nav\" (click)=\"goToPage(currentPage + 1)\" [disabled]=\"currentPage === totalPages || loading\">Next \u203A</button>\n <button class=\"osl-fg-page-btn osl-fg-page-btn--nav\" (click)=\"goToPage(totalPages)\" [disabled]=\"currentPage === totalPages || loading\">\u00BB</button>\n </div>\n\n <div class=\"osl-fg-pagination__size\">\n <select class=\"osl-fg-page-size\" [ngModel]=\"pageSize\" (ngModelChange)=\"onPageSizeChange($event)\" [disabled]=\"loading\">\n @for (opt of pageSizeOptions; track opt) {\n <option [value]=\"opt\">{{ opt }} per page</option>\n }\n </select>\n </div>\n\n </div>\n }\n\n </div><!-- /.osl-fg-grid-area -->\n @if (enableDragDrop) {\n <div class=\"osl-fg-panel\" [style.width.px]=\"leftPanelWidth\" [style.height]=\"tableHeight\">\n\n <!-- Panel header -->\n <div class=\"osl-fg-panel__header\">\n <span class=\"osl-fg-panel__title\">{{ dragTitle }}</span>\n @if (panelSearchType === 'Api' && panelTotalRecords > 0) {\n <span class=\"osl-fg-panel__count\">{{ draggingList.length }}/{{ panelTotalRecords }}</span>\n } @else if (filteredDraggingList.length > 0) {\n <span class=\"osl-fg-panel__count\">{{ filteredDraggingList.length }}</span>\n }\n </div>\n\n <!-- Search box -->\n @if (showSearch) {\n <div class=\"osl-fg-panel__search\">\n <svg class=\"osl-fg-search-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <circle cx=\"11\" cy=\"11\" r=\"8\"/><line x1=\"21\" y1=\"21\" x2=\"16.65\" y2=\"16.65\"/>\n </svg>\n <input\n class=\"osl-fg-search-input\"\n type=\"text\"\n [ngModel]=\"dragSearchQuery\"\n (ngModelChange)=\"onPanelSearchChange($event)\"\n placeholder=\"Search\u2026\"\n autocomplete=\"off\" />\n @if (dragSearchQuery) {\n <button class=\"osl-fg-search-clear\" (click)=\"clearPanelSearch()\" title=\"Clear\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"/><line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"/>\n </svg>\n </button>\n }\n </div>\n }\n\n <!-- Item list -->\n <div class=\"osl-fg-panel__list\"\n cdkDropList\n #availableList=\"cdkDropList\"\n [cdkDropListData]=\"filteredDraggingList\"\n [cdkDropListConnectedTo]=\"[gridDropZone]\"\n [cdkDropListSortingDisabled]=\"true\"\n (cdkDropListEntered)=\"onDragEnteredEvent($event, 'available')\"\n (cdkDropListExited)=\"onDragExitedEvent($event, 'available')\">\n\n <!-- Loading skeleton -->\n @if (panelLoading && draggingList.length === 0) {\n @for (sk of [1,2,3,4,5]; track sk) {\n <div class=\"osl-fg-item-skeleton\"></div>\n }\n\n } @else if (filteredDraggingList.length === 0) {\n <div class=\"osl-fg-panel__empty\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\">\n <circle cx=\"11\" cy=\"11\" r=\"8\"/><line x1=\"21\" y1=\"21\" x2=\"16.65\" y2=\"16.65\"/>\n </svg>\n <span>{{ dragSearchQuery ? 'No results found' : 'No results available' }}</span>\n </div>\n\n } @else {\n @for (item of filteredDraggingList; track item.id) {\n <div class=\"osl-fg-drag-item\"\n cdkDrag\n [cdkDragData]=\"item\"\n (cdkDragStarted)=\"onDragStartedEvent($event, item, 'available')\"\n (cdkDragEnded)=\"onDragEndedEvent($event, item, 'available')\">\n\n <!-- Grip handle -->\n <span class=\"osl-fg-drag-item__grip\">\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <circle cx=\"9\" cy=\"5\" r=\"1.4\"/><circle cx=\"9\" cy=\"12\" r=\"1.4\"/><circle cx=\"9\" cy=\"19\" r=\"1.4\"/>\n <circle cx=\"15\" cy=\"5\" r=\"1.4\"/><circle cx=\"15\" cy=\"12\" r=\"1.4\"/><circle cx=\"15\" cy=\"19\" r=\"1.4\"/>\n </svg>\n </span>\n\n <!-- Label + description -->\n <div class=\"osl-fg-drag-item__body\">\n <span class=\"osl-fg-drag-item__label\">{{ item.label }}</span>\n @if (item.description) {\n <span class=\"osl-fg-drag-item__desc\">{{ item.description }}</span>\n }\n </div>\n\n <!-- Drag-into-grid hint -->\n <span class=\"osl-fg-drag-item__arrow\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <line x1=\"5\" y1=\"12\" x2=\"19\" y2=\"12\"/><polyline points=\"12 5 19 12 12 19\"/>\n </svg>\n </span>\n\n <!-- Placeholder shown in panel while card is dragged -->\n <div *cdkDragPlaceholder class=\"osl-fg-item-placeholder\"></div>\n\n <!-- Preview under the cursor while dragging -->\n <ng-template cdkDragPreview>\n <div class=\"osl-fg-drag-preview\">{{ item.label }}</div>\n </ng-template>\n\n </div>\n }\n\n <!-- Load More (API mode) -->\n @if (panelHasMore) {\n <button class=\"osl-fg-load-more\" (click)=\"loadMorePanelItems()\" [disabled]=\"panelLoading\">\n @if (panelLoading) {\n <span class=\"osl-fg-load-more__spinner\"></span> Loading\u2026\n } @else {\n Load More\n }\n </button>\n }\n\n <!-- Inline loading indicator while appending -->\n @if (panelLoading && draggingList.length > 0) {\n <div class=\"osl-fg-item-skeleton\"></div>\n }\n }\n\n </div>\n </div>\n }\n</div><!-- /.osl-fg-wrapper -->\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-wrapper--builder{flex-direction:row;overflow:visible;align-items:stretch;gap:14px;border:none;background:transparent}.osl-fg-grid-area{flex:1 1 auto;min-width:0;display:flex;flex-direction:column;overflow:hidden;border-radius:inherit;position:relative}.osl-fg-wrapper--builder .osl-fg-grid-area{border:1px solid var(--osl-border-color, #e5e7eb);border-radius:var(--osl-border-radius, 8px);background:#fff}.osl-fg-table-drop-zone{position:absolute;inset:0;z-index:3;pointer-events:none;border-radius:inherit;transition:background .15s,border-color .15s}.osl-fg-table-drop-zone--active{border:2px dashed var(--osl-primary, #6366f1);background:#6366f108}.osl-fg-table-drop-zone--active.cdk-drop-list-receiving{background:#6366f114;border-style:solid}.osl-fg-drop-zone-badge{position:absolute;bottom:12px;left:50%;transform:translate(-50%);display:flex;align-items:center;gap:5px;padding:5px 13px;background:var(--osl-primary, #6366f1);color:#fff;border-radius:20px;font-size:11px;font-weight:600;white-space:nowrap;pointer-events:none;box-shadow:0 4px 12px #6366f159}.osl-fg-table-container{overflow-x:auto;overflow-y:auto;min-height:120px;flex:1 1 auto}.osl-fg-table-container--drop-active{outline:2px dashed var(--osl-primary, #6366f1);outline-offset:-2px}.osl-fg-table-container--drop-active .osl-fg-empty-inner{color:var(--osl-primary, #6366f1)}.osl-fg-table-container--drop-active .osl-fg-empty-inner svg{opacity:.7}.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--drag{width:28px;min-width:28px;padding:0}.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-td--drag{width:28px;min-width:28px;padding:0;text-align:center}.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-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}.osl-fg-row-handle{display:inline-flex;align-items:center;justify-content:center;width:22px;height:22px;cursor:grab;color:#d1d5db;border-radius:4px;transition:color .12s,background .12s}.osl-fg-row-handle svg{width:13px;height:13px}.osl-fg-row-handle:hover{color:#9ca3af;background:#f3f4f6}.osl-fg-row-handle:active{cursor:grabbing}.osl-fg-row-handle--disabled{cursor:not-allowed;color:#e5e7eb}.osl-fg-row-handle--disabled:hover{color:#e5e7eb;background:transparent}.osl-fg-row-handle--disabled:active{cursor:not-allowed}.osl-fg-row-placeholder td{padding:4px 6px;background:transparent;border:none}.osl-fg-drop-here{display:flex;align-items:center;justify-content:center;gap:8px;height:46px;border:2px dashed var(--osl-primary, #6366f1);border-radius:6px;background:#eef2ff;color:var(--osl-primary, #6366f1);font-size:12px;font-weight:600;letter-spacing:.03em}.osl-fg-drop-here svg{width:16px;height:16px;flex-shrink:0}.osl-fg-row-preview{display:flex;align-items:center;gap:14px;padding:9px 16px;background:#fff;border:1.5px solid var(--osl-primary, #6366f1);border-radius:8px;box-shadow:0 8px 24px #6366f12e;font-size:12px;color:#374151;max-width:420px;white-space:nowrap;overflow:hidden}.osl-fg-row-preview__cell{overflow:hidden;text-overflow:ellipsis;max-width:130px}.osl-fg-row-preview__cell+.osl-fg-row-preview__cell{padding-left:14px;border-left:1px solid #e5e7eb}.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-fg-panel{flex:0 0 auto;display:flex;flex-direction:column;border:1px solid var(--osl-border-color, #e5e7eb);border-radius:var(--osl-border-radius, 8px);background:linear-gradient(180deg,#eceafb,#e2defa);box-shadow:0 2px 6px #0f172a0f,0 8px 20px -6px #6366f129;min-height:200px;overflow:hidden}.osl-fg-panel__header{display:flex;align-items:center;justify-content:space-between;padding:11px 14px 10px;border-bottom:1px solid rgba(99,102,241,.18);background:#6366f11a;flex-shrink:0}.osl-fg-panel__title{font-size:11px;font-weight:700;color:#374151;text-transform:uppercase;letter-spacing:.06em}.osl-fg-panel__count{font-size:11px;font-weight:600;color:#9ca3af;background:#e5e7eb;border-radius:10px;padding:1px 7px;line-height:18px}.osl-fg-panel__search{position:relative;padding:8px 10px;border-bottom:1px solid rgba(99,102,241,.14);flex-shrink:0;background:#ffffff8c}.osl-fg-search-icon{position:absolute;left:20px;top:50%;transform:translateY(-50%);width:13px;height:13px;color:#d1d5db;pointer-events:none}.osl-fg-search-input{width:100%;height:30px;padding:0 26px 0 30px;border:1px solid var(--osl-border-color, #e5e7eb);border-radius:6px;background:#f9fafb;font-size:12px;font-family:inherit;color:#374151;outline:none;box-sizing:border-box;transition:border-color .15s,background .15s}.osl-fg-search-input:focus{border-color:var(--osl-primary, #6366f1);background:#fff}.osl-fg-search-input::placeholder{color:#d1d5db}.osl-fg-search-clear{position:absolute;right:16px;top:50%;transform:translateY(-50%);width:18px;height:18px;padding:0;border:none;background:transparent;color:#9ca3af;cursor:pointer;display:flex;align-items:center;justify-content:center;border-radius:3px}.osl-fg-search-clear svg{width:11px;height:11px}.osl-fg-search-clear:hover{color:#374151}.osl-fg-panel__list{flex:1 1 auto;min-height:0;overflow-y:auto;padding:8px;display:flex;flex-direction:column;gap:2px}.osl-fg-panel__empty{display:flex;flex-direction:column;align-items:center;gap:8px;padding:28px 12px;color:#c4c9d4;font-size:12px;text-align:center}.osl-fg-panel__empty svg{width:26px;height:26px;opacity:.5}.osl-fg-drag-item{display:flex;align-items:center;gap:9px;padding:8px 10px 8px 8px;border-radius:6px;cursor:grab;-webkit-user-select:none;user-select:none;transition:background .12s;background:transparent;position:relative}.osl-fg-drag-item:hover{background:#f0f4ff}.osl-fg-drag-item:hover .osl-fg-drag-item__grip{opacity:1;background:#6366f12e;color:var(--osl-primary, #6366f1)}.osl-fg-drag-item:hover .osl-fg-drag-item__arrow{opacity:.5}.osl-fg-drag-item:active{cursor:grabbing}.osl-fg-drag-item__grip{flex-shrink:0;width:20px;height:20px;opacity:.85;color:var(--osl-primary, #6366f1);background:#6366f11a;border-radius:4px;transition:opacity .15s,background .15s,color .15s;display:flex;align-items:center;justify-content:center}.osl-fg-drag-item__grip svg{width:13px;height:13px}.osl-fg-drag-item__body{flex:1 1 auto;min-width:0;display:flex;flex-direction:column;gap:1px}.osl-fg-drag-item__label{font-size:13px;font-weight:500;color:#1f2937;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;line-height:1.4}.osl-fg-drag-item__desc{font-size:11px;color:#9ca3af;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;line-height:1.3}.osl-fg-drag-item__arrow{flex-shrink:0;opacity:0;color:var(--osl-primary, #6366f1);transition:opacity .15s}.osl-fg-drag-item__arrow svg{width:13px;height:13px}.osl-fg-item-placeholder{height:36px;border:1.5px dashed #d1d5db;border-radius:6px;background:#f9fafb}.osl-fg-drag-preview{padding:8px 16px;background:var(--osl-primary, #6366f1);color:#fff;border-radius:8px;box-shadow:0 6px 18px #6366f159;font-size:12px;font-weight:600;white-space:nowrap;pointer-events:none}.osl-fg-item-skeleton{height:36px;border-radius:6px;background:#e5e7eb;animation:osl-fg-pulse 1.4s ease-in-out infinite;flex-shrink:0}.osl-fg-load-more{display:flex;align-items:center;justify-content:center;gap:6px;width:100%;height:32px;margin-top:4px;border:1px dashed var(--osl-border-color, #e5e7eb);border-radius:6px;background:transparent;color:#6b7280;font-size:12px;font-family:inherit;cursor:pointer;transition:border-color .15s,color .15s,background .15s;flex-shrink:0}.osl-fg-load-more:hover:not(:disabled){border-color:var(--osl-primary, #6366f1);color:var(--osl-primary, #6366f1);background:#f5f3ff}.osl-fg-load-more:disabled{opacity:.5;cursor:not-allowed}.osl-fg-load-more__spinner{width:12px;height:12px;border:2px solid currentColor;border-top-color:transparent;border-radius:50%;animation:osl-fg-spin .7s linear infinite;display:inline-block}@keyframes osl-fg-spin{to{transform:rotate(360deg)}}:host ::ng-deep .cdk-drag-animating{transition:transform .25s cubic-bezier(0,0,.2,1)}:host ::ng-deep .cdk-drop-list-dragging .osl-fg-drag-item:not(.cdk-drag-placeholder){transition:transform .25s cubic-bezier(0,0,.2,1)}\n"] }]
|
|
4532
|
-
}], propDecorators: { columns: [{
|
|
4605
|
+
args: [{ selector: 'osl-form-grid', standalone: false, providers: [DatePipe], template: "<div class=\"osl-fg-wrapper\" [class.osl-fg-wrapper--builder]=\"enableDragDrop\">\n\n <!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\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 Available Controls panel (only in builder mode)\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\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\n\n <!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\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 Main grid area (always present)\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\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-fg-grid-area\">\n\n <!-- Panel-to-grid drop overlay: always in DOM so #gridDropZone is always resolvable.\n pointer-events: none by default; only activates visually when isDraggingFromPanel.\n CDK hit-tests by bounding rect, not pointer-events, so drops are captured regardless. -->\n <div class=\"osl-fg-table-drop-zone\"\n [class.osl-fg-table-drop-zone--active]=\"isDraggingFromPanel\"\n cdkDropList\n #gridDropZone=\"cdkDropList\"\n [cdkDropListSortingDisabled]=\"true\"\n [cdkDropListDisabled]=\"!enableDragDrop\"\n [cdkDropListData]=\"[]\"\n (cdkDropListDropped)=\"onPanelDrop($event)\">\n @if (isDraggingFromPanel) {\n <div class=\"osl-fg-drop-zone-badge\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\" width=\"13\" height=\"13\">\n <path d=\"M12 5v14M5 12l7 7 7-7\"/>\n </svg>\n Drop to Add\n </div>\n }\n </div>\n\n <div class=\"osl-fg-table-container\"\n [style.height]=\"tableHeight\"\n [class.osl-fg-table-container--drop-active]=\"isDragging && enableDragDrop\">\n\n <table class=\"osl-fg-table\">\n\n <!-- \u2500\u2500 Header \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <thead class=\"osl-fg-thead\">\n <tr>\n @if (allowReorder) {\n <th class=\"osl-fg-th osl-fg-th--drag\"></th>\n }\n @if (hasActions) {\n <th class=\"osl-fg-th osl-fg-th--actions\">\n @if (canAdd) {\n <button class=\"osl-grid-icon-btn\" (click)=\"addRow()\" [disabled]=\"loading\" title=\"Add row\">\n <mat-icon>add</mat-icon>\n </button>\n }\n </th>\n }\n @for (col of columns; track col.key) {\n <th class=\"osl-fg-th\" [style.width]=\"col.width\">\n {{ col.displayName }}\n @if (colRequired(col)) {\n <span class=\"osl-fg-th-required\">*</span>\n }\n </th>\n }\n </tr>\n </thead>\n\n <!-- \u2500\u2500 Body \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 <tbody\n cdkDropList\n #gridList=\"cdkDropList\"\n [cdkDropListData]=\"pagedData\"\n [cdkDropListDisabled]=\"!allowReorder\"\n (cdkDropListDropped)=\"onGridReorder($event)\">\n\n <!-- Loading state -->\n @if (loading) {\n @for (sk of skeletonRows; track $index) {\n <tr class=\"osl-fg-row osl-fg-row--skeleton\">\n @if (allowReorder) {\n <td class=\"osl-fg-td osl-fg-td--drag\"></td>\n }\n @if (hasActions) {\n <td class=\"osl-fg-td osl-fg-td--actions\">\n <div class=\"osl-fg-skeleton osl-fg-skeleton--sm\"></div>\n </td>\n }\n @for (col of columns; track col.key) {\n <td class=\"osl-fg-td\"><div class=\"osl-fg-skeleton\"></div></td>\n }\n </tr>\n }\n\n <!-- Empty state -->\n } @else if (pagedData.length === 0) {\n <tr>\n <td [attr.colspan]=\"columns.length + (hasActions ? 1 : 0) + (allowReorder ? 1 : 0)\"\n class=\"osl-fg-empty\">\n <div class=\"osl-fg-empty-inner\">\n @if (enableDragDrop && isDragging) {\n <svg width=\"36\" height=\"36\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\">\n <path d=\"M12 5v14M5 12l7 7 7-7\"/>\n </svg>\n <p>Drop here</p>\n } @else {\n <svg width=\"36\" height=\"36\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\">\n <rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"2\"/>\n <line x1=\"3\" y1=\"9\" x2=\"21\" y2=\"9\"/><line x1=\"9\" y1=\"9\" x2=\"9\" y2=\"21\"/>\n </svg>\n <p>No rows yet.{{ canAdd ? ' Use + to get started.' : (enableDragDrop ? ' Drag a control here.' : '') }}</p>\n }\n </div>\n </td>\n </tr>\n\n <!-- Data rows -->\n } @else {\n @for (row of pagedData; track $index; let i = $index) {\n <tr class=\"osl-fg-row\"\n cdkDrag\n [cdkDragData]=\"row\"\n [cdkDragDisabled]=\"!allowReorder || isReorderDisabled(row)\"\n (cdkDragStarted)=\"onDragStartedEvent($event, row, 'grid')\"\n (cdkDragEnded)=\"onDragEndedEvent($event, row, 'grid')\"\n (click)=\"enableDragDrop ? selectGridItem(row, i) : null\">\n\n <!-- Drag handle -->\n @if (allowReorder) {\n <td class=\"osl-fg-td osl-fg-td--drag\">\n <span class=\"osl-fg-row-handle\"\n [class.osl-fg-row-handle--disabled]=\"isReorderDisabled(row)\"\n cdkDragHandle\n [cdkDragHandleDisabled]=\"isReorderDisabled(row)\"\n [title]=\"isReorderDisabled(row) ? 'Reorder disabled' : 'Drag to reorder'\">\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <circle cx=\"9\" cy=\"5\" r=\"1.3\"/><circle cx=\"9\" cy=\"12\" r=\"1.3\"/><circle cx=\"9\" cy=\"19\" r=\"1.3\"/>\n <circle cx=\"15\" cy=\"5\" r=\"1.3\"/><circle cx=\"15\" cy=\"12\" r=\"1.3\"/><circle cx=\"15\" cy=\"19\" r=\"1.3\"/>\n </svg>\n </span>\n </td>\n }\n\n <!-- Action cell (remove / delete) -->\n @if (hasActions) {\n <td class=\"osl-fg-td osl-fg-td--actions\">\n @if (enableDragDrop && allowRemove) {\n <button class=\"osl-grid-icon-btn osl-grid-icon-btn--danger\"\n (click)=\"removeGridRow(i); $event.stopPropagation()\" title=\"Remove\">\n <mat-icon>close</mat-icon>\n </button>\n } @else if (!enableDragDrop && canDelete) {\n <button class=\"osl-grid-icon-btn osl-grid-icon-btn--danger\"\n (click)=\"deleteRow(i)\" title=\"Delete\">\n <mat-icon>delete_outline</mat-icon>\n </button>\n }\n </td>\n }\n\n <!-- Data cells -->\n @for (col of columns; track col.key) {\n <td class=\"osl-fg-td\" [style.width]=\"col.width\">\n @if (col.formElem) {\n <div class=\"osl-fg-cell-form\" [style.width]=\"col.width\">\n @switch (col.formElem.elementType) {\n\n @case ('textbox') {\n <osl-input\n [label]=\"''\"\n [required]=\"false\"\n [disabled]=\"isDisabled(col.formElem,row,i)\"\n [type]=\"col.formElem.inputType || 'text'\"\n [placeholder]=\"col.formElem.placeholder || col.displayName\"\n [mask]=\"col.formElem.mask || ''\"\n [min]=\"col.formElem.min ?? ''\"\n [max]=\"col.formElem.max ?? ''\"\n [minLength]=\"col.formElem.minLength ?? null\"\n [maxLength]=\"col.formElem.maxLength ?? null\"\n [prefixIcon]=\"col.formElem.prefixIcon || ''\"\n [suffixIcon]=\"col.formElem.suffixIcon || ''\"\n [decimalPortion]=\"col.formElem.decimalPortion ?? null\"\n [model]=\"row[col.key]\"\n (modelChange)=\"onCellChange(row, col, $event)\"\n (changeEv)=\"col.formElem.change ? col.formElem.change(row,i) : null\">\n </osl-input>\n }\n\n @case ('textarea') {\n <osl-textarea\n [label]=\"''\"\n [required]=\"false\"\n [disabled]=\"isDisabled(col.formElem,row,i)\"\n [rows]=\"col.formElem.textareaRows || 3\"\n [placeholder]=\"col.formElem.placeholder || col.displayName\"\n [maxLength]=\"col.formElem.maxLength ?? null\"\n [minLength]=\"col.formElem.minLength ?? null\"\n [characterCounter]=\"!!col.formElem.characterCounter\"\n [resize]=\"col.formElem.resize || 'none'\"\n [model]=\"row[col.key]\"\n (modelChange)=\"onCellChange(row, col, $event)\"\n (changeEv)=\"col.formElem.change ? col.formElem.change(row,i) : null\">\n </osl-textarea>\n }\n\n @case ('select') {\n <osl-select\n [label]=\"''\"\n [required]=\"false\"\n [disabled]=\"isDisabled(col.formElem,row,i)\"\n [datasource]=\"col.formElem.datasource || []\"\n [displayField]=\"col.formElem.displayField || ''\"\n [valueField]=\"col.formElem.valueField || ''\"\n [placeholder]=\"col.formElem.selectPlaceholder || col.displayName\"\n [loading]=\"isLoading(row, col.formElem)\"\n [clearable]=\"!!col.formElem.clearable\"\n [model]=\"row[col.key]\"\n (modelChange)=\"onCellChange(row, col, $event)\"\n (changeEv)=\"col.formElem.change ? onSelectChange(col, row, i, $event) : null\">\n </osl-select>\n }\n\n @case ('autocomplete') {\n <osl-autocomplete\n [label]=\"''\"\n [required]=\"false\"\n [disabled]=\"isDisabled(col.formElem,row,i)\"\n [datasource]=\"col.formElem.datasource || []\"\n [displayField]=\"col.formElem.displayField || ''\"\n [valueField]=\"col.formElem.valueField || ''\"\n [placeholder]=\"col.formElem.autocompletePlaceholder || col.displayName\"\n [loading]=\"isLoading(row, col.formElem)\"\n [searchType]=\"col.formElem.searchType ? col.formElem.searchType : 'Local'\"\n [methodName]=\"col.formElem.apiMethod || ''\"\n [configMethodName]=\"col.formElem.apiConfigMethod || ''\"\n [service]=\"col.formElem.apiService\"\n [object]=\"col.formElem.objectName ? row[col.formElem.objectName] : null\"\n [isLister]=\"!!col.formElem.isListerAutocomplete\"\n [model]=\"row[col.key]\"\n (modelChange)=\"onCellChange(row, col, $event)\"\n (changeEv)=\"col.formElem.change ? onSelectChange(col, row, i, $event) : null\">\n </osl-autocomplete>\n }\n\n @case ('radio') {\n <osl-radio\n [label]=\"''\"\n [required]=\"false\"\n [disabled]=\"isDisabled(col.formElem,row,i)\"\n [datasource]=\"col.formElem.datasource || []\"\n [displayField]=\"col.formElem.displayField || ''\"\n [valueField]=\"col.formElem.valueField || ''\"\n [inline]=\"!!col.formElem.inline\"\n [model]=\"row[col.key]\"\n (modelChange)=\"onCellChange(row, col, $event)\"\n (changeEv)=\"col.formElem.change ? col.formElem.change(row,i) : null\">\n </osl-radio>\n }\n\n @case ('checkbox') {\n <osl-checkbox\n [label]=\"''\"\n [required]=\"false\"\n [disabled]=\"isDisabled(col.formElem,row,i)\"\n [indeterminate]=\"!!col.formElem.indeterminate\"\n [model]=\"row[col.key]\"\n (modelChange)=\"onCellChange(row, col, $event)\"\n (changeEv)=\"col.formElem.change ? col.formElem.change(row,i) : null\">\n </osl-checkbox>\n }\n\n @case ('slide-toggle') {\n <osl-slide-toggle\n [label]=\"''\"\n [disabled]=\"isDisabled(col.formElem,row,i)\"\n [labelPosition]=\"col.formElem.labelPosition || 'after'\"\n [trueLabel]=\"col.formElem.trueLabel || ''\"\n [falseLabel]=\"col.formElem.falseLabel || ''\"\n [model]=\"row[col.key]\"\n (modelChange)=\"onCellChange(row, col, $event)\"\n (changeEv)=\"col.formElem.change ? col.formElem.change(row,i) : null\">\n </osl-slide-toggle>\n }\n\n @case ('datepicker') {\n <osl-datepicker\n [label]=\"''\"\n [required]=\"false\"\n [disabled]=\"isDisabled(col.formElem,row,i)\"\n [dateType]=\"col.formElem.dateType || 'date'\"\n [placeholder]=\"col.formElem.placeholder || col.displayName\"\n [minDate]=\"col.formElem.minDate || ''\"\n [maxDate]=\"col.formElem.maxDate || ''\"\n [model]=\"row[col.key]\"\n (modelChange)=\"onCellChange(row, col, $event)\"\n (changeEv)=\"col.formElem.change ? col.formElem.change(row,i) : null\">\n </osl-datepicker>\n }\n\n @case ('datetimepicker') {\n <osl-datetimepicker\n [label]=\"''\"\n [required]=\"false\"\n [disabled]=\"isDisabled(col.formElem,row,i)\"\n [placeholder]=\"col.formElem.placeholder || col.displayName\"\n [minDatetime]=\"col.formElem.minDatetime || ''\"\n [maxDatetime]=\"col.formElem.maxDatetime || ''\"\n [format]=\"col.formElem.datetimepickerFormat || 'YYYY-MM-DDTHH:mm'\"\n [showSeconds]=\"!!col.formElem.datetimepickerShowSeconds\"\n [enableMeridian]=\"!!col.formElem.datetimepickerEnableMeridian\"\n [model]=\"row[col.key]\"\n (modelChange)=\"onCellChange(row, col, $event)\"\n (changeEv)=\"col.formElem.change ? col.formElem.change(row,i) : null\">\n </osl-datetimepicker>\n }\n\n @case ('file-uploader') {\n <osl-file-upload\n [label]=\"''\"\n [required]=\"false\"\n [disabled]=\"isDisabled(col.formElem,row,i)\"\n [accept]=\"col.formElem.accept || ''\"\n [multiple]=\"!!col.formElem.multiple\"\n [maxSize]=\"col.formElem.maxFileSize || 0\"\n [model]=\"row[col.key]\"\n (modelChange)=\"onCellChange(row, col, $event)\"\n (changeEv)=\"col.formElem.change ? col.formElem.change(row,i) : null\">\n </osl-file-upload>\n }\n\n @case ('templateRef') {\n <ng-container *ngTemplateOutlet=\"col.formElem.templateRef; context: { $implicit: row, index: i }\"></ng-container>\n }\n\n }\n </div>\n } @else {\n <span class=\"osl-fg-cell-text\">{{ row[col.key] ?? '--' }}</span>\n }\n </td>\n }\n\n <!-- Row drag preview -->\n <ng-template cdkDragPreview>\n <div class=\"osl-fg-row-preview\">\n @for (col of columns; track col.key) {\n <span class=\"osl-fg-row-preview__cell\">{{ getPreviewValue(col, row) }}</span>\n }\n </div>\n </ng-template>\n\n <!-- Drop placeholder: spans full row width with \"Drop Here\" -->\n <ng-template cdkDragPlaceholder>\n <tr class=\"osl-fg-row-placeholder\">\n <td [attr.colspan]=\"columns.length + (hasActions ? 1 : 0) + (allowReorder ? 1 : 0)\">\n <div class=\"osl-fg-drop-here\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M12 5v14M5 12l7 7 7-7\"/>\n </svg>\n Drop Here\n </div>\n </td>\n </tr>\n </ng-template>\n\n </tr>\n }\n }\n\n </tbody>\n\n <!-- \u2500\u2500 Footer \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 (footerColumns && footerColumns.length > 0 && datasource && datasource.length > 0) {\n <tfoot class=\"osl-fg-tfoot\">\n <tr class=\"osl-fg-footer-row\">\n @for (fcol of footerColumns; track $index) {\n <td\n [class]=\"'osl-fg-footer-td ' + (fcol.class || '')\"\n [attr.colspan]=\"fcol.colspan || 1\">\n {{ fcol.displayFn ? fcol.displayFn(datasource) : (fcol.display || '') }}\n </td>\n }\n </tr>\n </tfoot>\n }\n\n </table>\n </div>\n\n <!-- \u2500\u2500 Pagination (unchanged) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 (isPaginated) {\n <div class=\"osl-fg-pagination\">\n\n <span class=\"osl-fg-pagination__info\">\n @if (loading) {\n Loading...\n } @else if (_total > 0) {\n {{ startRecord }}\u2013{{ endRecord }} of {{ _total }} rows\n } @else {\n No rows\n }\n </span>\n\n <div class=\"osl-fg-pagination__controls\">\n <button class=\"osl-fg-page-btn osl-fg-page-btn--nav\" (click)=\"goToPage(1)\" [disabled]=\"currentPage === 1 || loading\">\u00AB</button>\n <button class=\"osl-fg-page-btn osl-fg-page-btn--nav\" (click)=\"goToPage(currentPage - 1)\" [disabled]=\"currentPage === 1 || loading\">\u2039 Prev</button>\n\n @for (page of pageNumbers; track $index) {\n @if (page === -1) {\n <span class=\"osl-fg-page-ellipsis\">\u2026</span>\n } @else {\n <button\n class=\"osl-fg-page-btn osl-fg-page-btn--num\"\n [class.osl-fg-page-btn--active]=\"page === currentPage\"\n [disabled]=\"loading\"\n (click)=\"goToPage(page)\">\n {{ page }}\n </button>\n }\n }\n\n <button class=\"osl-fg-page-btn osl-fg-page-btn--nav\" (click)=\"goToPage(currentPage + 1)\" [disabled]=\"currentPage === totalPages || loading\">Next \u203A</button>\n <button class=\"osl-fg-page-btn osl-fg-page-btn--nav\" (click)=\"goToPage(totalPages)\" [disabled]=\"currentPage === totalPages || loading\">\u00BB</button>\n </div>\n\n <div class=\"osl-fg-pagination__size\">\n <select class=\"osl-fg-page-size\" [ngModel]=\"pageSize\" (ngModelChange)=\"onPageSizeChange($event)\" [disabled]=\"loading\">\n @for (opt of pageSizeOptions; track opt) {\n <option [value]=\"opt\">{{ opt }} per page</option>\n }\n </select>\n </div>\n\n </div>\n }\n\n </div><!-- /.osl-fg-grid-area -->\n @if (enableDragDrop) {\n <div class=\"osl-fg-panel\" [style.width.px]=\"leftPanelWidth\" [style.height]=\"tableHeight\">\n\n <!-- Panel header -->\n <div class=\"osl-fg-panel__header\">\n <span class=\"osl-fg-panel__title\">{{ dragTitle }}</span>\n @if (panelSearchType === 'Api' && panelTotalRecords > 0) {\n <span class=\"osl-fg-panel__count\">{{ draggingList.length }}/{{ panelTotalRecords }}</span>\n } @else if (filteredDraggingList.length > 0) {\n <span class=\"osl-fg-panel__count\">{{ filteredDraggingList.length }}</span>\n }\n </div>\n\n <!-- Search box -->\n @if (showSearch) {\n <div class=\"osl-fg-panel__search\">\n <svg class=\"osl-fg-search-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <circle cx=\"11\" cy=\"11\" r=\"8\"/><line x1=\"21\" y1=\"21\" x2=\"16.65\" y2=\"16.65\"/>\n </svg>\n <input\n class=\"osl-fg-search-input\"\n type=\"text\"\n [ngModel]=\"dragSearchQuery\"\n (ngModelChange)=\"onPanelSearchChange($event)\"\n placeholder=\"Search\u2026\"\n autocomplete=\"off\" />\n @if (dragSearchQuery) {\n <button class=\"osl-fg-search-clear\" (click)=\"clearPanelSearch()\" title=\"Clear\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"/><line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"/>\n </svg>\n </button>\n }\n </div>\n }\n\n <!-- Item list -->\n <div class=\"osl-fg-panel__list\"\n cdkDropList\n #availableList=\"cdkDropList\"\n [cdkDropListData]=\"filteredDraggingList\"\n [cdkDropListConnectedTo]=\"[gridDropZone]\"\n [cdkDropListSortingDisabled]=\"true\"\n (cdkDropListEntered)=\"onDragEnteredEvent($event, 'available')\"\n (cdkDropListExited)=\"onDragExitedEvent($event, 'available')\">\n\n <!-- Loading skeleton -->\n @if (panelLoading && draggingList.length === 0) {\n @for (sk of [1,2,3,4,5]; track sk) {\n <div class=\"osl-fg-item-skeleton\"></div>\n }\n\n } @else if (filteredDraggingList.length === 0) {\n <div class=\"osl-fg-panel__empty\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\">\n <circle cx=\"11\" cy=\"11\" r=\"8\"/><line x1=\"21\" y1=\"21\" x2=\"16.65\" y2=\"16.65\"/>\n </svg>\n <span>{{ dragSearchQuery ? 'No results found' : 'No results available' }}</span>\n </div>\n\n } @else {\n @for (item of filteredDraggingList; track item.id) {\n <div class=\"osl-fg-drag-item\"\n cdkDrag\n [cdkDragData]=\"item\"\n (cdkDragStarted)=\"onDragStartedEvent($event, item, 'available')\"\n (cdkDragEnded)=\"onDragEndedEvent($event, item, 'available')\">\n\n <!-- Grip handle -->\n <span class=\"osl-fg-drag-item__grip\">\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <circle cx=\"9\" cy=\"5\" r=\"1.4\"/><circle cx=\"9\" cy=\"12\" r=\"1.4\"/><circle cx=\"9\" cy=\"19\" r=\"1.4\"/>\n <circle cx=\"15\" cy=\"5\" r=\"1.4\"/><circle cx=\"15\" cy=\"12\" r=\"1.4\"/><circle cx=\"15\" cy=\"19\" r=\"1.4\"/>\n </svg>\n </span>\n\n <!-- Label + description -->\n <div class=\"osl-fg-drag-item__body\">\n <span class=\"osl-fg-drag-item__label\">{{ item.label }}</span>\n @if (item.description) {\n <span class=\"osl-fg-drag-item__desc\">{{ item.description }}</span>\n }\n </div>\n\n <!-- Drag-into-grid hint -->\n <span class=\"osl-fg-drag-item__arrow\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <line x1=\"5\" y1=\"12\" x2=\"19\" y2=\"12\"/><polyline points=\"12 5 19 12 12 19\"/>\n </svg>\n </span>\n\n <!-- Placeholder shown in panel while card is dragged -->\n <div *cdkDragPlaceholder class=\"osl-fg-item-placeholder\"></div>\n\n <!-- Preview under the cursor while dragging -->\n <ng-template cdkDragPreview>\n <div class=\"osl-fg-drag-preview\">{{ item.label }}</div>\n </ng-template>\n\n </div>\n }\n\n <!-- Load More (API mode) -->\n @if (panelHasMore) {\n <button class=\"osl-fg-load-more\" (click)=\"loadMorePanelItems()\" [disabled]=\"panelLoading\">\n @if (panelLoading) {\n <span class=\"osl-fg-load-more__spinner\"></span> Loading\u2026\n } @else {\n Load More\n }\n </button>\n }\n\n <!-- Inline loading indicator while appending -->\n @if (panelLoading && draggingList.length > 0) {\n <div class=\"osl-fg-item-skeleton\"></div>\n }\n }\n\n </div>\n </div>\n }\n</div><!-- /.osl-fg-wrapper -->\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-wrapper--builder{flex-direction:row;overflow:visible;align-items:stretch;gap:14px;border:none;background:transparent}.osl-fg-grid-area{flex:1 1 auto;min-width:0;display:flex;flex-direction:column;overflow:hidden;border-radius:inherit;position:relative}.osl-fg-wrapper--builder .osl-fg-grid-area{border:1px solid var(--osl-border-color, #e5e7eb);border-radius:var(--osl-border-radius, 8px);background:#fff}.osl-fg-table-drop-zone{position:absolute;inset:0;z-index:3;pointer-events:none;border-radius:inherit;transition:background .15s,border-color .15s}.osl-fg-table-drop-zone--active{border:2px dashed var(--osl-primary, #6366f1);background:#6366f108}.osl-fg-table-drop-zone--active.cdk-drop-list-receiving{background:#6366f114;border-style:solid}.osl-fg-drop-zone-badge{position:absolute;bottom:12px;left:50%;transform:translate(-50%);display:flex;align-items:center;gap:5px;padding:5px 13px;background:var(--osl-primary, #6366f1);color:#fff;border-radius:20px;font-size:11px;font-weight:600;white-space:nowrap;pointer-events:none;box-shadow:0 4px 12px #6366f159}.osl-fg-table-container{overflow-x:auto;overflow-y:auto;min-height:120px;flex:1 1 auto}.osl-fg-table-container--drop-active{outline:2px dashed var(--osl-primary, #6366f1);outline-offset:-2px}.osl-fg-table-container--drop-active .osl-fg-empty-inner{color:var(--osl-primary, #6366f1)}.osl-fg-table-container--drop-active .osl-fg-empty-inner svg{opacity:.7}.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--drag{width:28px;min-width:28px;padding:0}.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-td--drag{width:28px;min-width:28px;padding:0;text-align:center}.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-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}.osl-fg-row-handle{display:inline-flex;align-items:center;justify-content:center;width:22px;height:22px;cursor:grab;color:#d1d5db;border-radius:4px;transition:color .12s,background .12s}.osl-fg-row-handle svg{width:13px;height:13px}.osl-fg-row-handle:hover{color:#9ca3af;background:#f3f4f6}.osl-fg-row-handle:active{cursor:grabbing}.osl-fg-row-handle--disabled{cursor:not-allowed;color:#e5e7eb}.osl-fg-row-handle--disabled:hover{color:#e5e7eb;background:transparent}.osl-fg-row-handle--disabled:active{cursor:not-allowed}.osl-fg-row-placeholder td{padding:4px 6px;background:transparent;border:none}.osl-fg-drop-here{display:flex;align-items:center;justify-content:center;gap:8px;height:46px;border:2px dashed var(--osl-primary, #6366f1);border-radius:6px;background:#eef2ff;color:var(--osl-primary, #6366f1);font-size:12px;font-weight:600;letter-spacing:.03em}.osl-fg-drop-here svg{width:16px;height:16px;flex-shrink:0}.osl-fg-row-preview{display:flex;align-items:center;gap:14px;padding:9px 16px;background:#fff;border:1.5px solid var(--osl-primary, #6366f1);border-radius:8px;box-shadow:0 8px 24px #6366f12e;font-size:12px;color:#374151;max-width:420px;white-space:nowrap;overflow:hidden}.osl-fg-row-preview__cell{overflow:hidden;text-overflow:ellipsis;max-width:130px}.osl-fg-row-preview__cell+.osl-fg-row-preview__cell{padding-left:14px;border-left:1px solid #e5e7eb}.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-fg-panel{flex:0 0 auto;display:flex;flex-direction:column;border:1px solid var(--osl-border-color, #e5e7eb);border-radius:var(--osl-border-radius, 8px);background:linear-gradient(180deg,#eceafb,#e2defa);box-shadow:0 2px 6px #0f172a0f,0 8px 20px -6px #6366f129;min-height:200px;overflow:hidden}.osl-fg-panel__header{display:flex;align-items:center;justify-content:space-between;padding:11px 14px 10px;border-bottom:1px solid rgba(99,102,241,.18);background:#6366f11a;flex-shrink:0}.osl-fg-panel__title{font-size:11px;font-weight:700;color:#374151;text-transform:uppercase;letter-spacing:.06em}.osl-fg-panel__count{font-size:11px;font-weight:600;color:#9ca3af;background:#e5e7eb;border-radius:10px;padding:1px 7px;line-height:18px}.osl-fg-panel__search{position:relative;padding:8px 10px;border-bottom:1px solid rgba(99,102,241,.14);flex-shrink:0;background:#ffffff8c}.osl-fg-search-icon{position:absolute;left:20px;top:50%;transform:translateY(-50%);width:13px;height:13px;color:#d1d5db;pointer-events:none}.osl-fg-search-input{width:100%;height:30px;padding:0 26px 0 30px;border:1px solid var(--osl-border-color, #e5e7eb);border-radius:6px;background:#f9fafb;font-size:12px;font-family:inherit;color:#374151;outline:none;box-sizing:border-box;transition:border-color .15s,background .15s}.osl-fg-search-input:focus{border-color:var(--osl-primary, #6366f1);background:#fff}.osl-fg-search-input::placeholder{color:#d1d5db}.osl-fg-search-clear{position:absolute;right:16px;top:50%;transform:translateY(-50%);width:18px;height:18px;padding:0;border:none;background:transparent;color:#9ca3af;cursor:pointer;display:flex;align-items:center;justify-content:center;border-radius:3px}.osl-fg-search-clear svg{width:11px;height:11px}.osl-fg-search-clear:hover{color:#374151}.osl-fg-panel__list{flex:1 1 auto;min-height:0;overflow-y:auto;padding:8px;display:flex;flex-direction:column;gap:2px}.osl-fg-panel__empty{display:flex;flex-direction:column;align-items:center;gap:8px;padding:28px 12px;color:#c4c9d4;font-size:12px;text-align:center}.osl-fg-panel__empty svg{width:26px;height:26px;opacity:.5}.osl-fg-drag-item{display:flex;align-items:center;gap:9px;padding:8px 10px 8px 8px;border-radius:6px;cursor:grab;-webkit-user-select:none;user-select:none;transition:background .12s;background:transparent;position:relative}.osl-fg-drag-item:hover{background:#f0f4ff}.osl-fg-drag-item:hover .osl-fg-drag-item__grip{opacity:1;background:#6366f12e;color:var(--osl-primary, #6366f1)}.osl-fg-drag-item:hover .osl-fg-drag-item__arrow{opacity:.5}.osl-fg-drag-item:active{cursor:grabbing}.osl-fg-drag-item__grip{flex-shrink:0;width:20px;height:20px;opacity:.85;color:var(--osl-primary, #6366f1);background:#6366f11a;border-radius:4px;transition:opacity .15s,background .15s,color .15s;display:flex;align-items:center;justify-content:center}.osl-fg-drag-item__grip svg{width:13px;height:13px}.osl-fg-drag-item__body{flex:1 1 auto;min-width:0;display:flex;flex-direction:column;gap:1px}.osl-fg-drag-item__label{font-size:13px;font-weight:500;color:#1f2937;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;line-height:1.4}.osl-fg-drag-item__desc{font-size:11px;color:#9ca3af;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;line-height:1.3}.osl-fg-drag-item__arrow{flex-shrink:0;opacity:0;color:var(--osl-primary, #6366f1);transition:opacity .15s}.osl-fg-drag-item__arrow svg{width:13px;height:13px}.osl-fg-item-placeholder{height:36px;border:1.5px dashed #d1d5db;border-radius:6px;background:#f9fafb}.osl-fg-drag-preview{padding:8px 16px;background:var(--osl-primary, #6366f1);color:#fff;border-radius:8px;box-shadow:0 6px 18px #6366f159;font-size:12px;font-weight:600;white-space:nowrap;pointer-events:none}.osl-fg-item-skeleton{height:36px;border-radius:6px;background:#e5e7eb;animation:osl-fg-pulse 1.4s ease-in-out infinite;flex-shrink:0}.osl-fg-load-more{display:flex;align-items:center;justify-content:center;gap:6px;width:100%;height:32px;margin-top:4px;border:1px dashed var(--osl-border-color, #e5e7eb);border-radius:6px;background:transparent;color:#6b7280;font-size:12px;font-family:inherit;cursor:pointer;transition:border-color .15s,color .15s,background .15s;flex-shrink:0}.osl-fg-load-more:hover:not(:disabled){border-color:var(--osl-primary, #6366f1);color:var(--osl-primary, #6366f1);background:#f5f3ff}.osl-fg-load-more:disabled{opacity:.5;cursor:not-allowed}.osl-fg-load-more__spinner{width:12px;height:12px;border:2px solid currentColor;border-top-color:transparent;border-radius:50%;animation:osl-fg-spin .7s linear infinite;display:inline-block}@keyframes osl-fg-spin{to{transform:rotate(360deg)}}:host ::ng-deep .cdk-drag-animating{transition:transform .25s cubic-bezier(0,0,.2,1)}:host ::ng-deep .cdk-drop-list-dragging .osl-fg-drag-item:not(.cdk-drag-placeholder){transition:transform .25s cubic-bezier(0,0,.2,1)}\n"] }]
|
|
4606
|
+
}], ctorParameters: () => [{ type: i1$1.DatePipe }], propDecorators: { columns: [{
|
|
4533
4607
|
type: Input,
|
|
4534
4608
|
args: ['columns']
|
|
4535
4609
|
}], datasource: [{
|