osl-base-extended 10.7.0 → 10.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4359,11 +4359,6 @@ class OslFormGrid {
4359
4359
  this.currentPage = this.totalPages;
4360
4360
  }
4361
4361
  }
4362
- autocompleteChange(col, ev) {
4363
- if (col && col.formElem && this.allowReorder) {
4364
- col.formElem.datasource = ev;
4365
- }
4366
- }
4367
4362
  onCellChange(row, col, value) {
4368
4363
  row[col.key] = value;
4369
4364
  this.datasourceChange.emit(this.datasource);
@@ -4620,11 +4615,11 @@ class OslFormGrid {
4620
4615
  });
4621
4616
  }
4622
4617
  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 });
4623
- 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 row; 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 (datasourceChange)=\"autocompleteChange(col,$event)\"\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"] }] });
4618
+ 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 row; 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 <!-- (datasourceChange)=\"col.formElem.datasource = $event\" -->\n <osl-autocomplete\n [label]=\"''\"\n [required]=\"false\"\n [disabled]=\"isDisabled(col.formElem,row,i)\"\n [datasource]=\"col.formElem.datasource || []\"\n \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"] }] });
4624
4619
  }
4625
4620
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: OslFormGrid, decorators: [{
4626
4621
  type: Component,
4627
- 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 row; 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 (datasourceChange)=\"autocompleteChange(col,$event)\"\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"] }]
4622
+ 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 row; 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 <!-- (datasourceChange)=\"col.formElem.datasource = $event\" -->\n <osl-autocomplete\n [label]=\"''\"\n [required]=\"false\"\n [disabled]=\"isDisabled(col.formElem,row,i)\"\n [datasource]=\"col.formElem.datasource || []\"\n \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"] }]
4628
4623
  }], ctorParameters: () => [{ type: i1$1.DatePipe }], propDecorators: { columns: [{
4629
4624
  type: Input,
4630
4625
  args: ['columns']