ng-fusion-ui 0.6.30 → 0.7.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.
- package/fesm2022/ng-fusion-ui.mjs +1635 -6
- package/fesm2022/ng-fusion-ui.mjs.map +1 -1
- package/lib/button/directives/button.directive.d.ts +2 -2
- package/lib/data-table/table/table.component.d.ts +6 -3
- package/lib/table/cell/cell.component.d.ts +20 -0
- package/lib/table/column/column.component.d.ts +113 -0
- package/lib/table/directives/button.directive.d.ts +16 -0
- package/lib/table/directives/click-outside.directive.d.ts +11 -0
- package/lib/table/directives/column-actions.directive.d.ts +62 -0
- package/lib/table/directives/column-cell.directive.d.ts +46 -0
- package/lib/table/directives/column-header-addon.directive.d.ts +25 -0
- package/lib/table/directives/column-header.directive.d.ts +25 -0
- package/lib/table/directives/row-context-menu.directive.d.ts +41 -0
- package/lib/table/directives/stop-propagation.directive.d.ts +22 -0
- package/lib/table/directives/table-expand-row.directive.d.ts +40 -0
- package/lib/table/directives/table-no-data.directive.d.ts +19 -0
- package/lib/table/directives/table-sidebar.directive.d.ts +19 -0
- package/lib/table/helpers/index.d.ts +96 -0
- package/lib/table/index.d.ts +16 -0
- package/lib/table/pipes/highlight.d.ts +60 -0
- package/lib/table/services/table-intl.service.d.ts +18 -0
- package/lib/table/table/table.component.d.ts +374 -0
- package/lib/table/table.module.d.ts +18 -0
- package/lib/table/types/index.d.ts +28 -0
- package/package.json +2 -1
- package/public-api.d.ts +1 -0
- package/styles/_button-tokens.scss +117 -0
- package/styles/_table-tokens.scss +43 -0
- package/styles/styles.scss +5 -4
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { booleanAttribute, Directive, Input, input, inject, ElementRef, Renderer2, NgModule, Injectable, Component, ChangeDetectionStrategy, ViewEncapsulation, HostBinding, signal, computed, effect, Pipe, model, contentChild, TemplateRef, DestroyRef, output, EventEmitter, untracked, Output, HostListener } from '@angular/core';
|
|
2
|
+
import { booleanAttribute, Directive, Input, input, inject, ElementRef, Renderer2, NgModule, Injectable, Component, ChangeDetectionStrategy, ViewEncapsulation, HostBinding, signal, computed, effect, Pipe, model, contentChild, TemplateRef, DestroyRef, Injector, output, EventEmitter, untracked, Output, HostListener, linkedSignal, viewChild, afterRenderEffect } from '@angular/core';
|
|
3
3
|
import * as i1$1 from '@angular/common';
|
|
4
|
-
import { CommonModule, NgTemplateOutlet } from '@angular/common';
|
|
4
|
+
import { CommonModule, NgTemplateOutlet, DatePipe } from '@angular/common';
|
|
5
5
|
import * as i1 from '@angular/platform-browser';
|
|
6
6
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
7
|
+
import * as i3 from '@angular/cdk/drag-drop';
|
|
8
|
+
import { moveItemInArray, DragDropModule } from '@angular/cdk/drag-drop';
|
|
9
|
+
import { CdkContextMenuTrigger, CdkMenu } from '@angular/cdk/menu';
|
|
10
|
+
import * as i2 from '@angular/forms';
|
|
11
|
+
import { FormsModule } from '@angular/forms';
|
|
7
12
|
|
|
8
13
|
/**
|
|
9
14
|
* @deprecated fu-btn-outline will be removed in next versions. Use `fuButton` instead.
|
|
@@ -900,8 +905,10 @@ class TableComponent {
|
|
|
900
905
|
this.popupOffset = input(0);
|
|
901
906
|
this.tableTitle = input();
|
|
902
907
|
this.variant = input('raised');
|
|
908
|
+
this.sideBar = input(false);
|
|
903
909
|
this.setsFirstPage$ = input();
|
|
904
910
|
this.expandIndex = model(null);
|
|
911
|
+
this.sidebarTemplate = contentChild('sidebarTemplate');
|
|
905
912
|
this.noRecordsTemplate = contentChild('noRecordsTemplate');
|
|
906
913
|
this.footerTemplate = contentChild('footerTemplate');
|
|
907
914
|
this.popupTemplate = contentChild(PopupTemplateDirective, {
|
|
@@ -923,6 +930,7 @@ class TableComponent {
|
|
|
923
930
|
this.tableIntl = inject(TableIntlService);
|
|
924
931
|
this.tableUtilityService = inject(TableUtilityService);
|
|
925
932
|
this.tableStorageService = inject(TableStorageService);
|
|
933
|
+
this.injector = inject(Injector);
|
|
926
934
|
this.noRecordsText = this.tableIntl.noRecordsText;
|
|
927
935
|
this.columnCount = this.tableUtilityService.headerCount;
|
|
928
936
|
this.pageSize = this.tableStorageService.pageSize;
|
|
@@ -1104,12 +1112,12 @@ class TableComponent {
|
|
|
1104
1112
|
this.hoveredRow.set(null);
|
|
1105
1113
|
}
|
|
1106
1114
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.6", ngImport: i0, type: TableComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
1107
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.0.6", type: TableComponent, isStandalone: true, selector: "fu-table", inputs: { dataSource: { classPropertyName: "dataSource", publicName: "dataSource", isSignal: true, isRequired: true, transformFunction: null }, localStorageKey: { classPropertyName: "localStorageKey", publicName: "localStorageKey", isSignal: true, isRequired: false, transformFunction: null }, tableLayout: { classPropertyName: "tableLayout", publicName: "tableLayout", isSignal: true, isRequired: false, transformFunction: null }, firstPageOnInit: { classPropertyName: "firstPageOnInit", publicName: "firstPageOnInit", isSignal: true, isRequired: false, transformFunction: null }, pageSizeOptions: { classPropertyName: "pageSizeOptions", publicName: "pageSizeOptions", isSignal: true, isRequired: false, transformFunction: null }, isLoading: { classPropertyName: "isLoading", publicName: "isLoading", isSignal: true, isRequired: false, transformFunction: null }, totalCount: { classPropertyName: "totalCount", publicName: "totalCount", isSignal: true, isRequired: false, transformFunction: null }, serverPagination: { classPropertyName: "serverPagination", publicName: "serverPagination", isSignal: true, isRequired: false, transformFunction: null }, stripedRows: { classPropertyName: "stripedRows", publicName: "stripedRows", isSignal: true, isRequired: false, transformFunction: null }, expandable: { classPropertyName: "expandable", publicName: "expandable", isSignal: true, isRequired: false, transformFunction: null }, expanded: { classPropertyName: "expanded", publicName: "expanded", isSignal: true, isRequired: false, transformFunction: null }, showFilter: { classPropertyName: "showFilter", publicName: "showFilter", isSignal: true, isRequired: false, transformFunction: null }, showPaginator: { classPropertyName: "showPaginator", publicName: "showPaginator", isSignal: true, isRequired: false, transformFunction: null }, quickPageJump: { classPropertyName: "quickPageJump", publicName: "quickPageJump", isSignal: true, isRequired: false, transformFunction: null }, popupOffset: { classPropertyName: "popupOffset", publicName: "popupOffset", isSignal: true, isRequired: false, transformFunction: null }, tableTitle: { classPropertyName: "tableTitle", publicName: "tableTitle", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, setsFirstPage$: { classPropertyName: "setsFirstPage$", publicName: "setsFirstPage$", isSignal: true, isRequired: false, transformFunction: null }, expandIndex: { classPropertyName: "expandIndex", publicName: "expandIndex", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { expandIndex: "expandIndexChange", tableActions: "tableActions", selectRowAction: "selectRowAction" }, host: { listeners: { "window:resize": "onResize()" } }, providers: [
|
|
1115
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.0.6", type: TableComponent, isStandalone: true, selector: "fu-table", inputs: { dataSource: { classPropertyName: "dataSource", publicName: "dataSource", isSignal: true, isRequired: true, transformFunction: null }, localStorageKey: { classPropertyName: "localStorageKey", publicName: "localStorageKey", isSignal: true, isRequired: false, transformFunction: null }, tableLayout: { classPropertyName: "tableLayout", publicName: "tableLayout", isSignal: true, isRequired: false, transformFunction: null }, firstPageOnInit: { classPropertyName: "firstPageOnInit", publicName: "firstPageOnInit", isSignal: true, isRequired: false, transformFunction: null }, pageSizeOptions: { classPropertyName: "pageSizeOptions", publicName: "pageSizeOptions", isSignal: true, isRequired: false, transformFunction: null }, isLoading: { classPropertyName: "isLoading", publicName: "isLoading", isSignal: true, isRequired: false, transformFunction: null }, totalCount: { classPropertyName: "totalCount", publicName: "totalCount", isSignal: true, isRequired: false, transformFunction: null }, serverPagination: { classPropertyName: "serverPagination", publicName: "serverPagination", isSignal: true, isRequired: false, transformFunction: null }, stripedRows: { classPropertyName: "stripedRows", publicName: "stripedRows", isSignal: true, isRequired: false, transformFunction: null }, expandable: { classPropertyName: "expandable", publicName: "expandable", isSignal: true, isRequired: false, transformFunction: null }, expanded: { classPropertyName: "expanded", publicName: "expanded", isSignal: true, isRequired: false, transformFunction: null }, showFilter: { classPropertyName: "showFilter", publicName: "showFilter", isSignal: true, isRequired: false, transformFunction: null }, showPaginator: { classPropertyName: "showPaginator", publicName: "showPaginator", isSignal: true, isRequired: false, transformFunction: null }, quickPageJump: { classPropertyName: "quickPageJump", publicName: "quickPageJump", isSignal: true, isRequired: false, transformFunction: null }, popupOffset: { classPropertyName: "popupOffset", publicName: "popupOffset", isSignal: true, isRequired: false, transformFunction: null }, tableTitle: { classPropertyName: "tableTitle", publicName: "tableTitle", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, sideBar: { classPropertyName: "sideBar", publicName: "sideBar", isSignal: true, isRequired: false, transformFunction: null }, setsFirstPage$: { classPropertyName: "setsFirstPage$", publicName: "setsFirstPage$", isSignal: true, isRequired: false, transformFunction: null }, expandIndex: { classPropertyName: "expandIndex", publicName: "expandIndex", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { expandIndex: "expandIndexChange", tableActions: "tableActions", selectRowAction: "selectRowAction" }, host: { listeners: { "window:resize": "onResize()" } }, providers: [
|
|
1108
1116
|
TableUtilityService,
|
|
1109
1117
|
TableStorageService,
|
|
1110
1118
|
TableSortService,
|
|
1111
1119
|
TableEditService,
|
|
1112
|
-
], queries: [{ propertyName: "noRecordsTemplate", first: true, predicate: ["noRecordsTemplate"], descendants: true, isSignal: true }, { propertyName: "footerTemplate", first: true, predicate: ["footerTemplate"], descendants: true, isSignal: true }, { propertyName: "popupTemplate", first: true, predicate: PopupTemplateDirective, descendants: true, read: TemplateRef, isSignal: true }, { propertyName: "expandTemplate", first: true, predicate: ExpandTemplateDirective, descendants: true, read: TemplateRef, isSignal: true }, { propertyName: "bodyTemplate", first: true, predicate: BodyTemplateDirective, descendants: true, read: TemplateRef, isSignal: true }, { propertyName: "headerTemplate", first: true, predicate: HeaderTemplateDirective, descendants: true, read: TemplateRef, isSignal: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"fu-table-wrapper\" [attr.outlined]=\"variant() === 'outlined'\">\r\n <!-- Search Filter -->\r\n @if (showFilter()) {\r\n <fu-table-filter [title]=\"tableTitle()\" />\r\n } @else if (tableTitle()) {\r\n <div style=\"margin-bottom: 12px\">\r\n <h3>{{ tableTitle() }}</h3>\r\n </div>\r\n }\r\n\r\n <!-- Table -->\r\n <table [attr.role]=\"'table'\" [style.table-layout]=\"tableLayout()\">\r\n <thead [attr.role]=\"'rowgroup'\">\r\n <tr [attr.role]=\"'row'\">\r\n @if (expandable()) {\r\n <th class=\"fu-expand-head\"></th>\r\n }\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n headerTemplate();\r\n context: { $implicit: dataSource()[0] | keyMapping }\r\n \"\r\n />\r\n </tr>\r\n </thead>\r\n\r\n <tbody [attr.role]=\"'rowgroup'\">\r\n @if (isInitLoad()) {\r\n <ng-container *ngTemplateOutlet=\"initLoadTemplate\" />\r\n } @else {\r\n @for (row of sortedData(); track $index; let odd = $odd) {\r\n <tr\r\n [attr.outlined]=\"variant() === 'outlined'\"\r\n [attr.role]=\"'row'\"\r\n class=\"fu-body-row\"\r\n [ngClass]=\"{\r\n striped: stripedRows() && odd,\r\n hoverable: expandable() || selectRowAction.observed,\r\n }\"\r\n (mouseenter)=\"onRowHover($event, row)\"\r\n (mouseleave)=\"onRowLeave()\"\r\n (click)=\"toggleRow(row, $index)\"\r\n >\r\n @if (expandable()) {\r\n <td\r\n class=\"fu-expand-cell\"\r\n [attr.data-expand]=\"expandedRowIndex === $index\"\r\n >\r\n <fu-icon size=\"16px\" iconName=\"chevronRight\" />\r\n </td>\r\n }\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n bodyTemplate();\r\n context: {\r\n $implicit: row,\r\n index: $index,\r\n keys: row | keyMapping,\r\n }\r\n \"\r\n />\r\n </tr>\r\n\r\n @if (\r\n (expandable() && expandedRowIndex === $index) ||\r\n expandIndex() === $index ||\r\n expanded()\r\n ) {\r\n <tr\r\n class=\"fu-expand-row\"\r\n [ngClass]=\"{\r\n striped: stripedRows() && odd && expanded(),\r\n }\"\r\n [attr.expanded]=\"expanded()\"\r\n (mouseenter)=\"onExpandHover()\"\r\n >\r\n <td [attr.colspan]=\"expandColumnSpan()\">\r\n @if (isLoading() && expanded()) {\r\n <span class=\"fu-skeleton-loader\"></span>\r\n } @else {\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n expandTemplate() || null;\r\n context: { $implicit: row, index: $index }\r\n \"\r\n />\r\n }\r\n </td>\r\n </tr>\r\n }\r\n } @empty {\r\n <tr class=\"fu-empty-row\">\r\n <td [attr.colspan]=\"expandColumnSpan()\">\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n noRecordsTemplate() || defaultNoRecordsTemplate\r\n \"\r\n />\r\n </td>\r\n </tr>\r\n }\r\n }\r\n </tbody>\r\n </table>\r\n\r\n @if (footerTemplate()) {\r\n <div class=\"fu-table-footer\">\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n footerTemplate() || null;\r\n context: { $implicit: sortedData() }\r\n \"\r\n />\r\n </div>\r\n }\r\n\r\n @if (showPaginator()) {\r\n <fu-table-paginator\r\n [dataLenght]=\"totalItems()\"\r\n [quickPageJump]=\"quickPageJump()\"\r\n [pageSizeOptions]=\"pageSizeOptions()\"\r\n />\r\n }\r\n</div>\r\n\r\n@if (hoveredRow()) {\r\n <div\r\n class=\"fu-action-popup\"\r\n [style.top.px]=\"popupPosition().top\"\r\n [style.right.px]=\"popupPosition().right\"\r\n (mouseenter)=\"onPopupHover()\"\r\n (mouseleave)=\"onPopupLeave()\"\r\n >\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n popupTemplate() || null;\r\n context: { $implicit: hoveredRow() }\r\n \"\r\n />\r\n </div>\r\n}\r\n\r\n<ng-template #defaultNoRecordsTemplate>\r\n <div class=\"fu-no-records\">\r\n <svg\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n width=\"24\"\r\n height=\"24\"\r\n viewBox=\"0 0 24 24\"\r\n >\r\n <path\r\n fill=\"none\"\r\n stroke=\"currentColor\"\r\n stroke-linecap=\"round\"\r\n stroke-linejoin=\"round\"\r\n stroke-width=\"1.5\"\r\n d=\"M12 8v4m0 4.01l.01-.011M9 3H4v3m0 5v2m16-2v2M15 3h5v3M9 21H4v-3m11 3h5v-3\"\r\n />\r\n </svg>\r\n <p>{{ noRecordsText() }}</p>\r\n </div>\r\n</ng-template>\r\n\r\n<ng-template #initLoadTemplate>\r\n @for (item of arrayFromSize(); track $index) {\r\n <tr class=\"fu-empty-row\">\r\n @for (item of arrayFromCols(); track $index) {\r\n <fu-tbody-cell />\r\n }\r\n </tr>\r\n }\r\n</ng-template>\r\n", styles: [".fu-table-wrapper{overflow-x:auto;padding:12px;font-family:Roboto,sans-serif;font-size:14px;background-color:var(--fu-table-bg-color);color:var(--fu-table-text-color);border-radius:4px}.fu-table-wrapper[outlined=true]{border:1px solid var(--fu-table-border-color)}.fu-table-wrapper[outlined=false]{box-shadow:0 1px 4px #0000005e}table{border-collapse:collapse;width:100%;margin-bottom:12px}thead tr{background-color:var(--fu-table-header-bg-color)}thead tr .fu-expand-head{width:40px;border-bottom:1px solid var(--fu-table-border-color)}@media (max-width: 900px){thead tr .fu-expand-head{display:none}}tbody tr .fu-expand-cell{border-bottom:1px solid var(--fu-table-border-color);text-align:center;vertical-align:middle}tbody tr .fu-expand-cell fu-icon{margin-top:3px}@media (max-width: 900px){tbody tr .fu-expand-cell{display:none}}tbody tr .fu-expand-cell[data-expand=true] fu-icon{transition:.1s ease-in-out;transform:rotate(90deg)}.fu-body-row{transition:.2s ease-in-out}.fu-body-row.striped{background-color:var(--fu-table-striped-row-bg-color)}.fu-body-row.hoverable:hover{background-color:var(--fu-table-bg-hover-color);cursor:pointer}.fu-expand-row[expanded=false]{border-top:1px solid var(--fu-table-border-color)}.fu-expand-row.striped{background-color:var(--fu-table-striped-row-bg-color)}tr.fu-expand-row>td{border-bottom:1px solid var(--fu-table-border-color);padding:8px}tr.fu-empty-row>td{border-bottom:1px solid var(--fu-table-border-color);border-top:1px solid var(--fu-table-border-color);padding:8px;text-align:center}tr.fu-empty-row>td svg{width:100px;height:100px;color:var(--fu-table-border-color)}@media (max-width: 900px){table{border:none;box-shadow:none;min-width:100%}thead{border-bottom:1px solid var(--fu-table-border-color)}tbody tr[outlined=true]{border-right:1px solid var(--fu-table-border-color);border-left:1px solid var(--fu-table-border-color);border-top:1px solid var(--fu-table-border-color)}tbody tr:first-child{border-top:none}tbody tr:last-child{margin-bottom:0}tr{display:block;margin-bottom:12px}tr[outlined=false]{box-shadow:0 2px 1px -1px #0003,0 1px 1px #00000024,0 1px 3px #0000001f}tr.fu-expand-row{margin-top:-8px;display:block;width:100%}tr.fu-expand-row>td{display:block;width:100%;padding:8px;box-sizing:border-box}tr.fu-empty-row>td{display:block;width:100%;padding:8px;box-sizing:border-box}}.fu-table-footer{position:relative;min-height:40px;margin-bottom:8px;background-color:var(--fu-table-header-bg-color);border-bottom:1px solid var(--fu-table-border-color);border-top:1px solid var(--fu-table-border-color)}.fu-action-popup{position:absolute;display:flex;align-items:center;gap:8px;background:var(--fu-table-bg-color);box-shadow:0 4px 6px #0000001a;border:1px solid var(--fu-table-border-color);border-radius:4px;min-height:40px;z-index:10;padding:0 8px}.fu-no-records{display:flex;flex-direction:column;justify-content:center;align-items:center;height:180px}.fu-no-records>p{color:#aeadad}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1$1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "ngmodule", type: IconModule }, { kind: "component", type: IconComponent, selector: "fu-icon", inputs: ["iconName", "size", "strokeWidth", "color"] }, { kind: "component", type: TablePaginatorComponent, selector: "fu-table-paginator", inputs: ["pageSizeOptions", "quickPageJump", "dataLenght"] }, { kind: "component", type: TableFilterComponent, selector: "fu-table-filter", inputs: ["title"] }, { kind: "component", type: TbodyCellComponent, selector: "fu-tbody-cell", inputs: ["cellValue", "editKey"] }, { kind: "pipe", type: KeyMappingPipe, name: "keyMapping" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
|
|
1120
|
+
], queries: [{ propertyName: "sidebarTemplate", first: true, predicate: ["sidebarTemplate"], descendants: true, isSignal: true }, { propertyName: "noRecordsTemplate", first: true, predicate: ["noRecordsTemplate"], descendants: true, isSignal: true }, { propertyName: "footerTemplate", first: true, predicate: ["footerTemplate"], descendants: true, isSignal: true }, { propertyName: "popupTemplate", first: true, predicate: PopupTemplateDirective, descendants: true, read: TemplateRef, isSignal: true }, { propertyName: "expandTemplate", first: true, predicate: ExpandTemplateDirective, descendants: true, read: TemplateRef, isSignal: true }, { propertyName: "bodyTemplate", first: true, predicate: BodyTemplateDirective, descendants: true, read: TemplateRef, isSignal: true }, { propertyName: "headerTemplate", first: true, predicate: HeaderTemplateDirective, descendants: true, read: TemplateRef, isSignal: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"fu-table-wrapper\" [attr.outlined]=\"variant() === 'outlined'\">\r\n <!-- Search Filter -->\r\n @if (showFilter()) {\r\n <fu-table-filter [title]=\"tableTitle()\" />\r\n } @else if (tableTitle()) {\r\n <div style=\"margin-bottom: 12px\">\r\n <h3>{{ tableTitle() }}</h3>\r\n </div>\r\n }\r\n\r\n <!-- Table -->\r\n <table [attr.role]=\"'table'\" [style.table-layout]=\"tableLayout()\">\r\n <thead [attr.role]=\"'rowgroup'\">\r\n <tr [attr.role]=\"'row'\">\r\n @if (expandable()) {\r\n <th class=\"fu-expand-head\"></th>\r\n }\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n headerTemplate();\r\n context: { $implicit: dataSource()[0] | keyMapping }\r\n \"\r\n />\r\n </tr>\r\n </thead>\r\n\r\n <tbody [attr.role]=\"'rowgroup'\">\r\n @if (sideBar()) {\r\n <div class=\"fu-sidebar\">\r\n <ng-container *ngTemplateOutlet=\"sidebarTemplate() || null\" />\r\n </div>\r\n }\r\n\r\n @if (isInitLoad()) {\r\n <ng-container *ngTemplateOutlet=\"initLoadTemplate\" />\r\n } @else {\r\n @for (row of sortedData(); track $index; let odd = $odd) {\r\n <tr\r\n [attr.outlined]=\"variant() === 'outlined'\"\r\n [attr.role]=\"'row'\"\r\n class=\"fu-body-row\"\r\n [ngClass]=\"{\r\n striped: stripedRows() && odd,\r\n hoverable: expandable() || selectRowAction.observed,\r\n }\"\r\n (mouseenter)=\"onRowHover($event, row)\"\r\n (mouseleave)=\"onRowLeave()\"\r\n (click)=\"toggleRow(row, $index)\"\r\n >\r\n @if (expandable()) {\r\n <td\r\n class=\"fu-expand-cell\"\r\n [attr.data-expand]=\"expandedRowIndex === $index\"\r\n >\r\n <fu-icon size=\"16px\" iconName=\"chevronRight\" />\r\n </td>\r\n }\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n bodyTemplate();\r\n context: {\r\n $implicit: row,\r\n index: $index,\r\n keys: row | keyMapping,\r\n }\r\n \"\r\n />\r\n </tr>\r\n\r\n @if (\r\n (expandable() && expandedRowIndex === $index) ||\r\n expandIndex() === $index ||\r\n expanded()\r\n ) {\r\n <tr\r\n class=\"fu-expand-row\"\r\n [ngClass]=\"{\r\n striped: stripedRows() && odd && expanded(),\r\n }\"\r\n [attr.expanded]=\"expanded()\"\r\n (mouseenter)=\"onExpandHover()\"\r\n >\r\n <td [attr.colspan]=\"expandColumnSpan()\">\r\n @if (isLoading() && expanded()) {\r\n <span class=\"fu-skeleton-loader\"></span>\r\n } @else {\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n expandTemplate() || null;\r\n context: { $implicit: row, index: $index }\r\n \"\r\n />\r\n }\r\n </td>\r\n </tr>\r\n }\r\n } @empty {\r\n <tr class=\"fu-empty-row\">\r\n <td [attr.colspan]=\"expandColumnSpan()\">\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n noRecordsTemplate() || defaultNoRecordsTemplate\r\n \"\r\n />\r\n </td>\r\n </tr>\r\n }\r\n }\r\n </tbody>\r\n </table>\r\n\r\n @if (footerTemplate()) {\r\n <div class=\"fu-table-footer\">\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n footerTemplate() || null;\r\n context: { $implicit: sortedData() }\r\n \"\r\n />\r\n </div>\r\n }\r\n\r\n @if (showPaginator()) {\r\n <fu-table-paginator\r\n [dataLenght]=\"totalItems()\"\r\n [quickPageJump]=\"quickPageJump()\"\r\n [pageSizeOptions]=\"pageSizeOptions()\"\r\n />\r\n }\r\n</div>\r\n\r\n@if (hoveredRow()) {\r\n <div\r\n class=\"fu-action-popup\"\r\n [style.top.px]=\"popupPosition().top\"\r\n [style.right.px]=\"popupPosition().right\"\r\n (mouseenter)=\"onPopupHover()\"\r\n (mouseleave)=\"onPopupLeave()\"\r\n >\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n popupTemplate() || null;\r\n context: { $implicit: hoveredRow() }\r\n \"\r\n />\r\n </div>\r\n}\r\n\r\n<ng-template #defaultNoRecordsTemplate>\r\n <div class=\"fu-no-records\">\r\n <svg\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n width=\"24\"\r\n height=\"24\"\r\n viewBox=\"0 0 24 24\"\r\n >\r\n <path\r\n fill=\"none\"\r\n stroke=\"currentColor\"\r\n stroke-linecap=\"round\"\r\n stroke-linejoin=\"round\"\r\n stroke-width=\"1.5\"\r\n d=\"M12 8v4m0 4.01l.01-.011M9 3H4v3m0 5v2m16-2v2M15 3h5v3M9 21H4v-3m11 3h5v-3\"\r\n />\r\n </svg>\r\n <p>{{ noRecordsText() }}</p>\r\n </div>\r\n</ng-template>\r\n\r\n<ng-template #initLoadTemplate>\r\n @for (item of arrayFromSize(); track $index) {\r\n <tr class=\"fu-empty-row\">\r\n @for (item of arrayFromCols(); track $index) {\r\n <fu-tbody-cell />\r\n }\r\n </tr>\r\n }\r\n</ng-template>\r\n", styles: [".fu-table-wrapper{position:relative;overflow-x:auto;padding:12px;font-family:Roboto,sans-serif;font-size:14px;background-color:var(--fu-table-bg-color);color:var(--fu-table-text-color);border-radius:4px}.fu-table-wrapper[outlined=true]{border:1px solid var(--fu-table-border-color)}.fu-table-wrapper[outlined=false]{box-shadow:0 1px 4px #0000005e}table{border-collapse:collapse;width:100%;margin-bottom:12px}thead tr{background-color:var(--fu-table-header-bg-color)}thead tr .fu-expand-head{width:40px;border-bottom:1px solid var(--fu-table-border-color)}@media (max-width: 900px){thead tr .fu-expand-head{display:none}}tbody tr .fu-expand-cell{border-bottom:1px solid var(--fu-table-border-color);text-align:center;vertical-align:middle}tbody tr .fu-expand-cell fu-icon{margin-top:3px}@media (max-width: 900px){tbody tr .fu-expand-cell{display:none}}tbody tr .fu-expand-cell[data-expand=true] fu-icon{transition:.1s ease-in-out;transform:rotate(90deg)}.fu-body-row{transition:.2s ease-in-out}.fu-body-row.striped{background-color:var(--fu-table-striped-row-bg-color)}.fu-body-row.hoverable:hover{background-color:var(--fu-table-bg-hover-color);cursor:pointer}.fu-expand-row[expanded=false]{border-top:1px solid var(--fu-table-border-color)}.fu-expand-row.striped{background-color:var(--fu-table-striped-row-bg-color)}tr.fu-expand-row>td{border-bottom:1px solid var(--fu-table-border-color);padding:8px}tr.fu-empty-row>td{border-bottom:1px solid var(--fu-table-border-color);border-top:1px solid var(--fu-table-border-color);padding:8px;text-align:center}tr.fu-empty-row>td svg{width:100px;height:100px;color:var(--fu-table-border-color)}@media (max-width: 900px){table{border:none;box-shadow:none;min-width:100%}thead{border-bottom:1px solid var(--fu-table-border-color)}tbody tr[outlined=true]{border-right:1px solid var(--fu-table-border-color);border-left:1px solid var(--fu-table-border-color);border-top:1px solid var(--fu-table-border-color)}tbody tr:first-child{border-top:none}tbody tr:last-child{margin-bottom:0}tr{display:block;margin-bottom:12px}tr[outlined=false]{box-shadow:0 2px 1px -1px #0003,0 1px 1px #00000024,0 1px 3px #0000001f}tr.fu-expand-row{margin-top:-8px;display:block;width:100%}tr.fu-expand-row>td{display:block;width:100%;padding:8px;box-sizing:border-box}tr.fu-empty-row>td{display:block;width:100%;padding:8px;box-sizing:border-box}}.fu-table-footer{position:relative;min-height:40px;margin-bottom:8px;background-color:var(--fu-table-header-bg-color);border-bottom:1px solid var(--fu-table-border-color);border-top:1px solid var(--fu-table-border-color)}.fu-action-popup{position:absolute;display:flex;align-items:center;gap:8px;background:var(--fu-table-bg-color);box-shadow:0 4px 6px #0000001a;border:1px solid var(--fu-table-border-color);border-radius:4px;min-height:40px;z-index:10;padding:0 8px}.fu-no-records{display:flex;flex-direction:column;justify-content:center;align-items:center;height:180px}.fu-no-records>p{color:#aeadad}tbody{position:relative}tbody .fu-sidebar{position:absolute;right:0;top:0;min-width:200px;height:100%;background-color:var(--fu-table-bg-color);border-left:2px solid var(--fu-table-border-color)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1$1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "ngmodule", type: IconModule }, { kind: "component", type: IconComponent, selector: "fu-icon", inputs: ["iconName", "size", "strokeWidth", "color"] }, { kind: "component", type: TablePaginatorComponent, selector: "fu-table-paginator", inputs: ["pageSizeOptions", "quickPageJump", "dataLenght"] }, { kind: "component", type: TableFilterComponent, selector: "fu-table-filter", inputs: ["title"] }, { kind: "component", type: TbodyCellComponent, selector: "fu-tbody-cell", inputs: ["cellValue", "editKey"] }, { kind: "pipe", type: KeyMappingPipe, name: "keyMapping" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
|
|
1113
1121
|
}
|
|
1114
1122
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.6", ngImport: i0, type: TableComponent, decorators: [{
|
|
1115
1123
|
type: Component,
|
|
@@ -1126,7 +1134,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.6", ngImpor
|
|
|
1126
1134
|
TableStorageService,
|
|
1127
1135
|
TableSortService,
|
|
1128
1136
|
TableEditService,
|
|
1129
|
-
], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: "<div class=\"fu-table-wrapper\" [attr.outlined]=\"variant() === 'outlined'\">\r\n <!-- Search Filter -->\r\n @if (showFilter()) {\r\n <fu-table-filter [title]=\"tableTitle()\" />\r\n } @else if (tableTitle()) {\r\n <div style=\"margin-bottom: 12px\">\r\n <h3>{{ tableTitle() }}</h3>\r\n </div>\r\n }\r\n\r\n <!-- Table -->\r\n <table [attr.role]=\"'table'\" [style.table-layout]=\"tableLayout()\">\r\n <thead [attr.role]=\"'rowgroup'\">\r\n <tr [attr.role]=\"'row'\">\r\n @if (expandable()) {\r\n <th class=\"fu-expand-head\"></th>\r\n }\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n headerTemplate();\r\n context: { $implicit: dataSource()[0] | keyMapping }\r\n \"\r\n />\r\n </tr>\r\n </thead>\r\n\r\n <tbody [attr.role]=\"'rowgroup'\">\r\n @if (isInitLoad()) {\r\n <ng-container *ngTemplateOutlet=\"initLoadTemplate\" />\r\n } @else {\r\n @for (row of sortedData(); track $index; let odd = $odd) {\r\n <tr\r\n [attr.outlined]=\"variant() === 'outlined'\"\r\n [attr.role]=\"'row'\"\r\n class=\"fu-body-row\"\r\n [ngClass]=\"{\r\n striped: stripedRows() && odd,\r\n hoverable: expandable() || selectRowAction.observed,\r\n }\"\r\n (mouseenter)=\"onRowHover($event, row)\"\r\n (mouseleave)=\"onRowLeave()\"\r\n (click)=\"toggleRow(row, $index)\"\r\n >\r\n @if (expandable()) {\r\n <td\r\n class=\"fu-expand-cell\"\r\n [attr.data-expand]=\"expandedRowIndex === $index\"\r\n >\r\n <fu-icon size=\"16px\" iconName=\"chevronRight\" />\r\n </td>\r\n }\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n bodyTemplate();\r\n context: {\r\n $implicit: row,\r\n index: $index,\r\n keys: row | keyMapping,\r\n }\r\n \"\r\n />\r\n </tr>\r\n\r\n @if (\r\n (expandable() && expandedRowIndex === $index) ||\r\n expandIndex() === $index ||\r\n expanded()\r\n ) {\r\n <tr\r\n class=\"fu-expand-row\"\r\n [ngClass]=\"{\r\n striped: stripedRows() && odd && expanded(),\r\n }\"\r\n [attr.expanded]=\"expanded()\"\r\n (mouseenter)=\"onExpandHover()\"\r\n >\r\n <td [attr.colspan]=\"expandColumnSpan()\">\r\n @if (isLoading() && expanded()) {\r\n <span class=\"fu-skeleton-loader\"></span>\r\n } @else {\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n expandTemplate() || null;\r\n context: { $implicit: row, index: $index }\r\n \"\r\n />\r\n }\r\n </td>\r\n </tr>\r\n }\r\n } @empty {\r\n <tr class=\"fu-empty-row\">\r\n <td [attr.colspan]=\"expandColumnSpan()\">\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n noRecordsTemplate() || defaultNoRecordsTemplate\r\n \"\r\n />\r\n </td>\r\n </tr>\r\n }\r\n }\r\n </tbody>\r\n </table>\r\n\r\n @if (footerTemplate()) {\r\n <div class=\"fu-table-footer\">\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n footerTemplate() || null;\r\n context: { $implicit: sortedData() }\r\n \"\r\n />\r\n </div>\r\n }\r\n\r\n @if (showPaginator()) {\r\n <fu-table-paginator\r\n [dataLenght]=\"totalItems()\"\r\n [quickPageJump]=\"quickPageJump()\"\r\n [pageSizeOptions]=\"pageSizeOptions()\"\r\n />\r\n }\r\n</div>\r\n\r\n@if (hoveredRow()) {\r\n <div\r\n class=\"fu-action-popup\"\r\n [style.top.px]=\"popupPosition().top\"\r\n [style.right.px]=\"popupPosition().right\"\r\n (mouseenter)=\"onPopupHover()\"\r\n (mouseleave)=\"onPopupLeave()\"\r\n >\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n popupTemplate() || null;\r\n context: { $implicit: hoveredRow() }\r\n \"\r\n />\r\n </div>\r\n}\r\n\r\n<ng-template #defaultNoRecordsTemplate>\r\n <div class=\"fu-no-records\">\r\n <svg\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n width=\"24\"\r\n height=\"24\"\r\n viewBox=\"0 0 24 24\"\r\n >\r\n <path\r\n fill=\"none\"\r\n stroke=\"currentColor\"\r\n stroke-linecap=\"round\"\r\n stroke-linejoin=\"round\"\r\n stroke-width=\"1.5\"\r\n d=\"M12 8v4m0 4.01l.01-.011M9 3H4v3m0 5v2m16-2v2M15 3h5v3M9 21H4v-3m11 3h5v-3\"\r\n />\r\n </svg>\r\n <p>{{ noRecordsText() }}</p>\r\n </div>\r\n</ng-template>\r\n\r\n<ng-template #initLoadTemplate>\r\n @for (item of arrayFromSize(); track $index) {\r\n <tr class=\"fu-empty-row\">\r\n @for (item of arrayFromCols(); track $index) {\r\n <fu-tbody-cell />\r\n }\r\n </tr>\r\n }\r\n</ng-template>\r\n", styles: [".fu-table-wrapper{overflow-x:auto;padding:12px;font-family:Roboto,sans-serif;font-size:14px;background-color:var(--fu-table-bg-color);color:var(--fu-table-text-color);border-radius:4px}.fu-table-wrapper[outlined=true]{border:1px solid var(--fu-table-border-color)}.fu-table-wrapper[outlined=false]{box-shadow:0 1px 4px #0000005e}table{border-collapse:collapse;width:100%;margin-bottom:12px}thead tr{background-color:var(--fu-table-header-bg-color)}thead tr .fu-expand-head{width:40px;border-bottom:1px solid var(--fu-table-border-color)}@media (max-width: 900px){thead tr .fu-expand-head{display:none}}tbody tr .fu-expand-cell{border-bottom:1px solid var(--fu-table-border-color);text-align:center;vertical-align:middle}tbody tr .fu-expand-cell fu-icon{margin-top:3px}@media (max-width: 900px){tbody tr .fu-expand-cell{display:none}}tbody tr .fu-expand-cell[data-expand=true] fu-icon{transition:.1s ease-in-out;transform:rotate(90deg)}.fu-body-row{transition:.2s ease-in-out}.fu-body-row.striped{background-color:var(--fu-table-striped-row-bg-color)}.fu-body-row.hoverable:hover{background-color:var(--fu-table-bg-hover-color);cursor:pointer}.fu-expand-row[expanded=false]{border-top:1px solid var(--fu-table-border-color)}.fu-expand-row.striped{background-color:var(--fu-table-striped-row-bg-color)}tr.fu-expand-row>td{border-bottom:1px solid var(--fu-table-border-color);padding:8px}tr.fu-empty-row>td{border-bottom:1px solid var(--fu-table-border-color);border-top:1px solid var(--fu-table-border-color);padding:8px;text-align:center}tr.fu-empty-row>td svg{width:100px;height:100px;color:var(--fu-table-border-color)}@media (max-width: 900px){table{border:none;box-shadow:none;min-width:100%}thead{border-bottom:1px solid var(--fu-table-border-color)}tbody tr[outlined=true]{border-right:1px solid var(--fu-table-border-color);border-left:1px solid var(--fu-table-border-color);border-top:1px solid var(--fu-table-border-color)}tbody tr:first-child{border-top:none}tbody tr:last-child{margin-bottom:0}tr{display:block;margin-bottom:12px}tr[outlined=false]{box-shadow:0 2px 1px -1px #0003,0 1px 1px #00000024,0 1px 3px #0000001f}tr.fu-expand-row{margin-top:-8px;display:block;width:100%}tr.fu-expand-row>td{display:block;width:100%;padding:8px;box-sizing:border-box}tr.fu-empty-row>td{display:block;width:100%;padding:8px;box-sizing:border-box}}.fu-table-footer{position:relative;min-height:40px;margin-bottom:8px;background-color:var(--fu-table-header-bg-color);border-bottom:1px solid var(--fu-table-border-color);border-top:1px solid var(--fu-table-border-color)}.fu-action-popup{position:absolute;display:flex;align-items:center;gap:8px;background:var(--fu-table-bg-color);box-shadow:0 4px 6px #0000001a;border:1px solid var(--fu-table-border-color);border-radius:4px;min-height:40px;z-index:10;padding:0 8px}.fu-no-records{display:flex;flex-direction:column;justify-content:center;align-items:center;height:180px}.fu-no-records>p{color:#aeadad}\n"] }]
|
|
1137
|
+
], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: "<div class=\"fu-table-wrapper\" [attr.outlined]=\"variant() === 'outlined'\">\r\n <!-- Search Filter -->\r\n @if (showFilter()) {\r\n <fu-table-filter [title]=\"tableTitle()\" />\r\n } @else if (tableTitle()) {\r\n <div style=\"margin-bottom: 12px\">\r\n <h3>{{ tableTitle() }}</h3>\r\n </div>\r\n }\r\n\r\n <!-- Table -->\r\n <table [attr.role]=\"'table'\" [style.table-layout]=\"tableLayout()\">\r\n <thead [attr.role]=\"'rowgroup'\">\r\n <tr [attr.role]=\"'row'\">\r\n @if (expandable()) {\r\n <th class=\"fu-expand-head\"></th>\r\n }\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n headerTemplate();\r\n context: { $implicit: dataSource()[0] | keyMapping }\r\n \"\r\n />\r\n </tr>\r\n </thead>\r\n\r\n <tbody [attr.role]=\"'rowgroup'\">\r\n @if (sideBar()) {\r\n <div class=\"fu-sidebar\">\r\n <ng-container *ngTemplateOutlet=\"sidebarTemplate() || null\" />\r\n </div>\r\n }\r\n\r\n @if (isInitLoad()) {\r\n <ng-container *ngTemplateOutlet=\"initLoadTemplate\" />\r\n } @else {\r\n @for (row of sortedData(); track $index; let odd = $odd) {\r\n <tr\r\n [attr.outlined]=\"variant() === 'outlined'\"\r\n [attr.role]=\"'row'\"\r\n class=\"fu-body-row\"\r\n [ngClass]=\"{\r\n striped: stripedRows() && odd,\r\n hoverable: expandable() || selectRowAction.observed,\r\n }\"\r\n (mouseenter)=\"onRowHover($event, row)\"\r\n (mouseleave)=\"onRowLeave()\"\r\n (click)=\"toggleRow(row, $index)\"\r\n >\r\n @if (expandable()) {\r\n <td\r\n class=\"fu-expand-cell\"\r\n [attr.data-expand]=\"expandedRowIndex === $index\"\r\n >\r\n <fu-icon size=\"16px\" iconName=\"chevronRight\" />\r\n </td>\r\n }\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n bodyTemplate();\r\n context: {\r\n $implicit: row,\r\n index: $index,\r\n keys: row | keyMapping,\r\n }\r\n \"\r\n />\r\n </tr>\r\n\r\n @if (\r\n (expandable() && expandedRowIndex === $index) ||\r\n expandIndex() === $index ||\r\n expanded()\r\n ) {\r\n <tr\r\n class=\"fu-expand-row\"\r\n [ngClass]=\"{\r\n striped: stripedRows() && odd && expanded(),\r\n }\"\r\n [attr.expanded]=\"expanded()\"\r\n (mouseenter)=\"onExpandHover()\"\r\n >\r\n <td [attr.colspan]=\"expandColumnSpan()\">\r\n @if (isLoading() && expanded()) {\r\n <span class=\"fu-skeleton-loader\"></span>\r\n } @else {\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n expandTemplate() || null;\r\n context: { $implicit: row, index: $index }\r\n \"\r\n />\r\n }\r\n </td>\r\n </tr>\r\n }\r\n } @empty {\r\n <tr class=\"fu-empty-row\">\r\n <td [attr.colspan]=\"expandColumnSpan()\">\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n noRecordsTemplate() || defaultNoRecordsTemplate\r\n \"\r\n />\r\n </td>\r\n </tr>\r\n }\r\n }\r\n </tbody>\r\n </table>\r\n\r\n @if (footerTemplate()) {\r\n <div class=\"fu-table-footer\">\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n footerTemplate() || null;\r\n context: { $implicit: sortedData() }\r\n \"\r\n />\r\n </div>\r\n }\r\n\r\n @if (showPaginator()) {\r\n <fu-table-paginator\r\n [dataLenght]=\"totalItems()\"\r\n [quickPageJump]=\"quickPageJump()\"\r\n [pageSizeOptions]=\"pageSizeOptions()\"\r\n />\r\n }\r\n</div>\r\n\r\n@if (hoveredRow()) {\r\n <div\r\n class=\"fu-action-popup\"\r\n [style.top.px]=\"popupPosition().top\"\r\n [style.right.px]=\"popupPosition().right\"\r\n (mouseenter)=\"onPopupHover()\"\r\n (mouseleave)=\"onPopupLeave()\"\r\n >\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n popupTemplate() || null;\r\n context: { $implicit: hoveredRow() }\r\n \"\r\n />\r\n </div>\r\n}\r\n\r\n<ng-template #defaultNoRecordsTemplate>\r\n <div class=\"fu-no-records\">\r\n <svg\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n width=\"24\"\r\n height=\"24\"\r\n viewBox=\"0 0 24 24\"\r\n >\r\n <path\r\n fill=\"none\"\r\n stroke=\"currentColor\"\r\n stroke-linecap=\"round\"\r\n stroke-linejoin=\"round\"\r\n stroke-width=\"1.5\"\r\n d=\"M12 8v4m0 4.01l.01-.011M9 3H4v3m0 5v2m16-2v2M15 3h5v3M9 21H4v-3m11 3h5v-3\"\r\n />\r\n </svg>\r\n <p>{{ noRecordsText() }}</p>\r\n </div>\r\n</ng-template>\r\n\r\n<ng-template #initLoadTemplate>\r\n @for (item of arrayFromSize(); track $index) {\r\n <tr class=\"fu-empty-row\">\r\n @for (item of arrayFromCols(); track $index) {\r\n <fu-tbody-cell />\r\n }\r\n </tr>\r\n }\r\n</ng-template>\r\n", styles: [".fu-table-wrapper{position:relative;overflow-x:auto;padding:12px;font-family:Roboto,sans-serif;font-size:14px;background-color:var(--fu-table-bg-color);color:var(--fu-table-text-color);border-radius:4px}.fu-table-wrapper[outlined=true]{border:1px solid var(--fu-table-border-color)}.fu-table-wrapper[outlined=false]{box-shadow:0 1px 4px #0000005e}table{border-collapse:collapse;width:100%;margin-bottom:12px}thead tr{background-color:var(--fu-table-header-bg-color)}thead tr .fu-expand-head{width:40px;border-bottom:1px solid var(--fu-table-border-color)}@media (max-width: 900px){thead tr .fu-expand-head{display:none}}tbody tr .fu-expand-cell{border-bottom:1px solid var(--fu-table-border-color);text-align:center;vertical-align:middle}tbody tr .fu-expand-cell fu-icon{margin-top:3px}@media (max-width: 900px){tbody tr .fu-expand-cell{display:none}}tbody tr .fu-expand-cell[data-expand=true] fu-icon{transition:.1s ease-in-out;transform:rotate(90deg)}.fu-body-row{transition:.2s ease-in-out}.fu-body-row.striped{background-color:var(--fu-table-striped-row-bg-color)}.fu-body-row.hoverable:hover{background-color:var(--fu-table-bg-hover-color);cursor:pointer}.fu-expand-row[expanded=false]{border-top:1px solid var(--fu-table-border-color)}.fu-expand-row.striped{background-color:var(--fu-table-striped-row-bg-color)}tr.fu-expand-row>td{border-bottom:1px solid var(--fu-table-border-color);padding:8px}tr.fu-empty-row>td{border-bottom:1px solid var(--fu-table-border-color);border-top:1px solid var(--fu-table-border-color);padding:8px;text-align:center}tr.fu-empty-row>td svg{width:100px;height:100px;color:var(--fu-table-border-color)}@media (max-width: 900px){table{border:none;box-shadow:none;min-width:100%}thead{border-bottom:1px solid var(--fu-table-border-color)}tbody tr[outlined=true]{border-right:1px solid var(--fu-table-border-color);border-left:1px solid var(--fu-table-border-color);border-top:1px solid var(--fu-table-border-color)}tbody tr:first-child{border-top:none}tbody tr:last-child{margin-bottom:0}tr{display:block;margin-bottom:12px}tr[outlined=false]{box-shadow:0 2px 1px -1px #0003,0 1px 1px #00000024,0 1px 3px #0000001f}tr.fu-expand-row{margin-top:-8px;display:block;width:100%}tr.fu-expand-row>td{display:block;width:100%;padding:8px;box-sizing:border-box}tr.fu-empty-row>td{display:block;width:100%;padding:8px;box-sizing:border-box}}.fu-table-footer{position:relative;min-height:40px;margin-bottom:8px;background-color:var(--fu-table-header-bg-color);border-bottom:1px solid var(--fu-table-border-color);border-top:1px solid var(--fu-table-border-color)}.fu-action-popup{position:absolute;display:flex;align-items:center;gap:8px;background:var(--fu-table-bg-color);box-shadow:0 4px 6px #0000001a;border:1px solid var(--fu-table-border-color);border-radius:4px;min-height:40px;z-index:10;padding:0 8px}.fu-no-records{display:flex;flex-direction:column;justify-content:center;align-items:center;height:180px}.fu-no-records>p{color:#aeadad}tbody{position:relative}tbody .fu-sidebar{position:absolute;right:0;top:0;min-width:200px;height:100%;background-color:var(--fu-table-bg-color);border-left:2px solid var(--fu-table-border-color)}\n"] }]
|
|
1130
1138
|
}], propDecorators: { selectRowAction: [{
|
|
1131
1139
|
type: Output
|
|
1132
1140
|
}], onResize: [{
|
|
@@ -1380,6 +1388,1627 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.6", ngImpor
|
|
|
1380
1388
|
}]
|
|
1381
1389
|
}] });
|
|
1382
1390
|
|
|
1391
|
+
/**
|
|
1392
|
+
* Defines a custom expand-row template for a `fu-table`.
|
|
1393
|
+
*
|
|
1394
|
+
* The template context exposes:
|
|
1395
|
+
* - `row` – the current row data
|
|
1396
|
+
* - `index` – row index
|
|
1397
|
+
* - `searchTerm` – current search term
|
|
1398
|
+
*
|
|
1399
|
+
* ## Typing
|
|
1400
|
+
* To enable strong typing for `row`, provide the table data using `rowOf`.
|
|
1401
|
+
* This is used only for type inference and does not affect rendering.
|
|
1402
|
+
*
|
|
1403
|
+
* Usage:
|
|
1404
|
+
* ```html
|
|
1405
|
+
* <ng-template fuTableExpandRow let-row let-index="index">
|
|
1406
|
+
* {{ row.name }} {{row.age}}
|
|
1407
|
+
* </ng-template>
|
|
1408
|
+
* ```
|
|
1409
|
+
*
|
|
1410
|
+
* If `rowOf` is omitted, `row` will be typed as `any`.
|
|
1411
|
+
*/
|
|
1412
|
+
class FuTableExpandRowTemplateDirective {
|
|
1413
|
+
constructor() {
|
|
1414
|
+
/**
|
|
1415
|
+
* Provides typing context for the expand row template.
|
|
1416
|
+
* Used only for type inference – not consumed at runtime.
|
|
1417
|
+
*/
|
|
1418
|
+
this.of = input(null, {
|
|
1419
|
+
alias: 'rowOf',
|
|
1420
|
+
});
|
|
1421
|
+
}
|
|
1422
|
+
static ngTemplateContextGuard(_dir, ctx) {
|
|
1423
|
+
return true;
|
|
1424
|
+
}
|
|
1425
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.6", ngImport: i0, type: FuTableExpandRowTemplateDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
1426
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "19.0.6", type: FuTableExpandRowTemplateDirective, isStandalone: true, selector: "[fuTableExpandRow]", inputs: { of: { classPropertyName: "of", publicName: "rowOf", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 }); }
|
|
1427
|
+
}
|
|
1428
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.6", ngImport: i0, type: FuTableExpandRowTemplateDirective, decorators: [{
|
|
1429
|
+
type: Directive,
|
|
1430
|
+
args: [{
|
|
1431
|
+
selector: '[fuTableExpandRow]',
|
|
1432
|
+
}]
|
|
1433
|
+
}] });
|
|
1434
|
+
|
|
1435
|
+
/**
|
|
1436
|
+
* Defines a custom no data template for a `fu-table`.
|
|
1437
|
+
*
|
|
1438
|
+
* Usage:
|
|
1439
|
+
* ```html
|
|
1440
|
+
* <ng-template fuTableNoData >
|
|
1441
|
+
* no data available
|
|
1442
|
+
* </ng-template>
|
|
1443
|
+
* ```
|
|
1444
|
+
*/
|
|
1445
|
+
class FuTableNoDataTemplateDirective {
|
|
1446
|
+
static ngTemplateContextGuard(_dir, ctx) {
|
|
1447
|
+
return true;
|
|
1448
|
+
}
|
|
1449
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.6", ngImport: i0, type: FuTableNoDataTemplateDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
1450
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.0.6", type: FuTableNoDataTemplateDirective, isStandalone: true, selector: "[fuTableNoData]", ngImport: i0 }); }
|
|
1451
|
+
}
|
|
1452
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.6", ngImport: i0, type: FuTableNoDataTemplateDirective, decorators: [{
|
|
1453
|
+
type: Directive,
|
|
1454
|
+
args: [{
|
|
1455
|
+
selector: '[fuTableNoData]',
|
|
1456
|
+
}]
|
|
1457
|
+
}] });
|
|
1458
|
+
|
|
1459
|
+
/**
|
|
1460
|
+
* Defines a custom sidebar template for a `fu-table`.
|
|
1461
|
+
*
|
|
1462
|
+
* Usage:
|
|
1463
|
+
* ```html
|
|
1464
|
+
* <ng-template fuTableSidebar >
|
|
1465
|
+
* custom content
|
|
1466
|
+
* </ng-template>
|
|
1467
|
+
* ```
|
|
1468
|
+
*/
|
|
1469
|
+
class FuTableSidebarTemplateDirective {
|
|
1470
|
+
static ngTemplateContextGuard(_dir, ctx) {
|
|
1471
|
+
return true;
|
|
1472
|
+
}
|
|
1473
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.6", ngImport: i0, type: FuTableSidebarTemplateDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
1474
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.0.6", type: FuTableSidebarTemplateDirective, isStandalone: true, selector: "[fuTableSidebar]", ngImport: i0 }); }
|
|
1475
|
+
}
|
|
1476
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.6", ngImport: i0, type: FuTableSidebarTemplateDirective, decorators: [{
|
|
1477
|
+
type: Directive,
|
|
1478
|
+
args: [{
|
|
1479
|
+
selector: '[fuTableSidebar]',
|
|
1480
|
+
}]
|
|
1481
|
+
}] });
|
|
1482
|
+
|
|
1483
|
+
class FuClickOutsideDirective {
|
|
1484
|
+
constructor(el) {
|
|
1485
|
+
this.el = el;
|
|
1486
|
+
this.fuClickOutside = output();
|
|
1487
|
+
}
|
|
1488
|
+
onPointerDown(event) {
|
|
1489
|
+
const target = event.target;
|
|
1490
|
+
if (!(target instanceof Node))
|
|
1491
|
+
return;
|
|
1492
|
+
if (!this.el.nativeElement.contains(target)) {
|
|
1493
|
+
this.fuClickOutside.emit();
|
|
1494
|
+
}
|
|
1495
|
+
}
|
|
1496
|
+
onEscape() {
|
|
1497
|
+
this.fuClickOutside.emit();
|
|
1498
|
+
}
|
|
1499
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.6", ngImport: i0, type: FuClickOutsideDirective, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
1500
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.0.6", type: FuClickOutsideDirective, isStandalone: true, selector: "[fuClickOutside]", outputs: { fuClickOutside: "fuClickOutside" }, host: { listeners: { "document:pointerdown": "onPointerDown($event)", "document:keydown.escape": "onEscape()" } }, ngImport: i0 }); }
|
|
1501
|
+
}
|
|
1502
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.6", ngImport: i0, type: FuClickOutsideDirective, decorators: [{
|
|
1503
|
+
type: Directive,
|
|
1504
|
+
args: [{
|
|
1505
|
+
selector: '[fuClickOutside]',
|
|
1506
|
+
}]
|
|
1507
|
+
}], ctorParameters: () => [{ type: i0.ElementRef }], propDecorators: { onPointerDown: [{
|
|
1508
|
+
type: HostListener,
|
|
1509
|
+
args: ['document:pointerdown', ['$event']]
|
|
1510
|
+
}], onEscape: [{
|
|
1511
|
+
type: HostListener,
|
|
1512
|
+
args: ['document:keydown.escape']
|
|
1513
|
+
}] } });
|
|
1514
|
+
|
|
1515
|
+
/**
|
|
1516
|
+
* Defines a custom row context menu template for a `fu-column`.
|
|
1517
|
+
*
|
|
1518
|
+
* The template context exposes:
|
|
1519
|
+
* - `row` – the current row data
|
|
1520
|
+
* - `index` – row index
|
|
1521
|
+
* - `trigger` – the context menu trigger
|
|
1522
|
+
*
|
|
1523
|
+
* ## Typing
|
|
1524
|
+
* To enable strong typing for `row`, provide the table data using `rowOf`.
|
|
1525
|
+
* This is used only for type inference and does not affect rendering.
|
|
1526
|
+
*
|
|
1527
|
+
* Usage:
|
|
1528
|
+
* ```html
|
|
1529
|
+
* <ng-template fuRowContextMenu let-row="row" let-index="index">
|
|
1530
|
+
* {{ row.name }}
|
|
1531
|
+
* </ng-template>
|
|
1532
|
+
* ```
|
|
1533
|
+
*
|
|
1534
|
+
* If `rowOf` is omitted, `row` will be typed as `any`.
|
|
1535
|
+
*/
|
|
1536
|
+
class FuRowContextMenuTemplateDirective {
|
|
1537
|
+
constructor() {
|
|
1538
|
+
/**
|
|
1539
|
+
* Provides typing context for the row context menu template.
|
|
1540
|
+
* Used only for type inference – not consumed at runtime.
|
|
1541
|
+
*/
|
|
1542
|
+
this.of = input(null, {
|
|
1543
|
+
alias: 'rowOf',
|
|
1544
|
+
});
|
|
1545
|
+
}
|
|
1546
|
+
static ngTemplateContextGuard(_dir, ctx) {
|
|
1547
|
+
return true;
|
|
1548
|
+
}
|
|
1549
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.6", ngImport: i0, type: FuRowContextMenuTemplateDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
1550
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "19.0.6", type: FuRowContextMenuTemplateDirective, isStandalone: true, selector: "[fuRowContextMenu]", inputs: { of: { classPropertyName: "of", publicName: "rowOf", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 }); }
|
|
1551
|
+
}
|
|
1552
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.6", ngImport: i0, type: FuRowContextMenuTemplateDirective, decorators: [{
|
|
1553
|
+
type: Directive,
|
|
1554
|
+
args: [{
|
|
1555
|
+
selector: '[fuRowContextMenu]',
|
|
1556
|
+
}]
|
|
1557
|
+
}] });
|
|
1558
|
+
|
|
1559
|
+
/**
|
|
1560
|
+
* Prevents click handlers from being triggered.
|
|
1561
|
+
*
|
|
1562
|
+
* ## Usage
|
|
1563
|
+
* ```html
|
|
1564
|
+
* <button fuStopPropagation>Save</button>
|
|
1565
|
+
*
|
|
1566
|
+
* <input fuStopPropagation />
|
|
1567
|
+
*
|
|
1568
|
+
* <button fuStopPropagation (click)="editRow(row)">✏️</button>
|
|
1569
|
+
* ```
|
|
1570
|
+
*
|
|
1571
|
+
* ## Notes
|
|
1572
|
+
* - Does **not** call `preventDefault()`
|
|
1573
|
+
* - Safe to use on form controls
|
|
1574
|
+
* - Intended for use inside `fu-table` rows
|
|
1575
|
+
*/
|
|
1576
|
+
class FuStopPropagationDirective {
|
|
1577
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.6", ngImport: i0, type: FuStopPropagationDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
1578
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.0.6", type: FuStopPropagationDirective, isStandalone: true, selector: "[fuStopPropagation]", host: { listeners: { "click": "$event.stopPropagation()", "mousedown": "$event.stopPropagation()" } }, ngImport: i0 }); }
|
|
1579
|
+
}
|
|
1580
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.6", ngImport: i0, type: FuStopPropagationDirective, decorators: [{
|
|
1581
|
+
type: Directive,
|
|
1582
|
+
args: [{
|
|
1583
|
+
selector: '[fuStopPropagation]',
|
|
1584
|
+
host: {
|
|
1585
|
+
'(click)': '$event.stopPropagation()',
|
|
1586
|
+
'(mousedown)': '$event.stopPropagation()',
|
|
1587
|
+
},
|
|
1588
|
+
}]
|
|
1589
|
+
}] });
|
|
1590
|
+
|
|
1591
|
+
/**
|
|
1592
|
+
* Highlights occurrences of a search term inside a string by wrapping matches
|
|
1593
|
+
* in a `<mark>` element.
|
|
1594
|
+
*
|
|
1595
|
+
* This pipe is primarily intended for use with table cells and search results.
|
|
1596
|
+
* It returns an HTML string and is typically used together with `[innerHTML]`.
|
|
1597
|
+
*
|
|
1598
|
+
* ## Parameters
|
|
1599
|
+
* - `value` – the text value to highlight
|
|
1600
|
+
* - `term` – the search term to highlight
|
|
1601
|
+
* - `active` (optional) – whether the match should be marked as the active match
|
|
1602
|
+
*
|
|
1603
|
+
* If `term` is empty or `value` is `null`/`undefined`, the original value is
|
|
1604
|
+
* returned as a string.
|
|
1605
|
+
*
|
|
1606
|
+
* Special characters in the search term are safely escaped.
|
|
1607
|
+
*
|
|
1608
|
+
* ## Usage
|
|
1609
|
+
* ```html
|
|
1610
|
+
* <span
|
|
1611
|
+
* [innerHTML]="value | fuHighlight : searchTerm : isActive"
|
|
1612
|
+
* ></span>
|
|
1613
|
+
* ```
|
|
1614
|
+
*
|
|
1615
|
+
* ## Styling
|
|
1616
|
+
* The pipe adds the following CSS classes:
|
|
1617
|
+
* - `search-highlight` – applied to all matches
|
|
1618
|
+
* - `search-highlight active` – applied when `active` is `true`
|
|
1619
|
+
*
|
|
1620
|
+
* Example:
|
|
1621
|
+
* ```css
|
|
1622
|
+
* .search-highlight {
|
|
1623
|
+
* background-color: var(--fu-search-highlight-bg);
|
|
1624
|
+
* }
|
|
1625
|
+
*
|
|
1626
|
+
* .search-highlight.active {
|
|
1627
|
+
* background-color: var(--fu-search-highlight-active-bg);
|
|
1628
|
+
* }
|
|
1629
|
+
* ```
|
|
1630
|
+
*
|
|
1631
|
+
* ## Notes
|
|
1632
|
+
* - This pipe returns **HTML**, not plain text.
|
|
1633
|
+
* - Always bind using `[innerHTML]`.
|
|
1634
|
+
* - The pipe is stateless and does not manage search state internally.
|
|
1635
|
+
*/
|
|
1636
|
+
class FuHighlightPipe {
|
|
1637
|
+
/**
|
|
1638
|
+
* Highlights occurrences of `term` in `value` by wrapping them in `<mark>`.
|
|
1639
|
+
*
|
|
1640
|
+
* @param value Text to transform
|
|
1641
|
+
* @param term Search term to highlight
|
|
1642
|
+
* @param active Whether the match is the active one
|
|
1643
|
+
* @returns HTML string with highlighted matches
|
|
1644
|
+
*/
|
|
1645
|
+
transform(value, term, active = false) {
|
|
1646
|
+
if (!term || value == null)
|
|
1647
|
+
return String(value ?? '');
|
|
1648
|
+
const escaped = term.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
1649
|
+
const regex = new RegExp(`(${escaped})`, 'gi');
|
|
1650
|
+
return String(value).replace(regex, match => active
|
|
1651
|
+
? `<mark class="search-highlight active">${match}</mark>`
|
|
1652
|
+
: `<mark class="search-highlight">${match}</mark>`);
|
|
1653
|
+
}
|
|
1654
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.6", ngImport: i0, type: FuHighlightPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
|
|
1655
|
+
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "19.0.6", ngImport: i0, type: FuHighlightPipe, isStandalone: true, name: "fuHighlight" }); }
|
|
1656
|
+
}
|
|
1657
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.6", ngImport: i0, type: FuHighlightPipe, decorators: [{
|
|
1658
|
+
type: Pipe,
|
|
1659
|
+
args: [{ name: 'fuHighlight' }]
|
|
1660
|
+
}] });
|
|
1661
|
+
|
|
1662
|
+
class FuCellComponent {
|
|
1663
|
+
constructor() {
|
|
1664
|
+
this.value = model();
|
|
1665
|
+
this.type = input();
|
|
1666
|
+
this.editing = input(false);
|
|
1667
|
+
this.editable = input(false);
|
|
1668
|
+
this.searchTerm = input('');
|
|
1669
|
+
this.activeMatch = input(false);
|
|
1670
|
+
this.loading = input(false);
|
|
1671
|
+
this.valueType = computed(() => {
|
|
1672
|
+
return this.type() ?? typeof this.value();
|
|
1673
|
+
});
|
|
1674
|
+
this.dateValue = linkedSignal(() => {
|
|
1675
|
+
if (this.type() === 'date') {
|
|
1676
|
+
return this.value().toISOString().slice(0, 10);
|
|
1677
|
+
}
|
|
1678
|
+
});
|
|
1679
|
+
this.cellValueChange = output();
|
|
1680
|
+
}
|
|
1681
|
+
dateChange(event) {
|
|
1682
|
+
const input = event.target;
|
|
1683
|
+
const value = input.valueAsDate;
|
|
1684
|
+
this.cellValueChange.emit(value);
|
|
1685
|
+
}
|
|
1686
|
+
numberChange(value) {
|
|
1687
|
+
this.cellValueChange.emit(value);
|
|
1688
|
+
}
|
|
1689
|
+
booleanChange(value) {
|
|
1690
|
+
this.cellValueChange.emit(value);
|
|
1691
|
+
}
|
|
1692
|
+
textChange(value) {
|
|
1693
|
+
this.cellValueChange.emit(value);
|
|
1694
|
+
}
|
|
1695
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.6", ngImport: i0, type: FuCellComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
1696
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.0.6", type: FuCellComponent, isStandalone: true, selector: "fu-cell", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null }, editing: { classPropertyName: "editing", publicName: "editing", isSignal: true, isRequired: false, transformFunction: null }, editable: { classPropertyName: "editable", publicName: "editable", isSignal: true, isRequired: false, transformFunction: null }, searchTerm: { classPropertyName: "searchTerm", publicName: "searchTerm", isSignal: true, isRequired: false, transformFunction: null }, activeMatch: { classPropertyName: "activeMatch", publicName: "activeMatch", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", cellValueChange: "cellValueChange" }, ngImport: i0, template: "@if (editable() && editing()) {\r\n @switch (valueType()) {\r\n @case ('number') {\r\n <input\r\n type=\"number\"\r\n [(ngModel)]=\"value\"\r\n (ngModelChange)=\"numberChange($event)\"\r\n fuStopPropagation\r\n />\r\n }\r\n\r\n @case ('boolean') {\r\n <input\r\n type=\"checkbox\"\r\n [(ngModel)]=\"value\"\r\n (ngModelChange)=\"booleanChange($event)\"\r\n fuStopPropagation\r\n />\r\n }\r\n\r\n @case ('date') {\r\n <input\r\n type=\"date\"\r\n [(ngModel)]=\"dateValue\"\r\n (change)=\"dateChange($event)\"\r\n fuStopPropagation\r\n />\r\n }\r\n\r\n @case ('string') {\r\n <input\r\n type=\"text\"\r\n [(ngModel)]=\"value\"\r\n (ngModelChange)=\"textChange($event)\"\r\n fuStopPropagation\r\n />\r\n }\r\n\r\n @case ('text') {\r\n <input\r\n type=\"text\"\r\n [(ngModel)]=\"value\"\r\n (ngModelChange)=\"textChange($event)\"\r\n fuStopPropagation\r\n />\r\n }\r\n }\r\n} @else {\r\n <div>\r\n @switch (valueType()) {\r\n @case ('date') {\r\n <span>{{ value() | date }}</span>\r\n }\r\n\r\n @case ('text') {\r\n <span\r\n [innerHTML]=\"value() | fuHighlight: searchTerm() : activeMatch()\"\r\n >{{ value() }}</span\r\n >\r\n }\r\n\r\n @case ('number') {\r\n <span\r\n [innerHTML]=\"value() | fuHighlight: searchTerm() : activeMatch()\"\r\n >{{ value() }}</span\r\n >\r\n }\r\n\r\n @case ('boolean') {\r\n <input type=\"checkbox\" [disabled]=\"true\" [checked]=\"value()\" />\r\n }\r\n\r\n @default {\r\n <span\r\n [innerHTML]=\"value() | fuHighlight: searchTerm() : activeMatch()\"\r\n >{{ value() }}</span\r\n >\r\n }\r\n }\r\n </div>\r\n}\r\n", styles: ["input{padding:6px 8px;font-size:14px;line-height:1.4;border:1px solid var(--fu-table-border);border-radius:var(--fu-border-radius);background-color:var(--fu-table-input-bg);color:var(--fu-table-text);outline:none;transition:border-color .15s,box-shadow .15s}input[type=number],input[type=text]{width:100%}input::placeholder{color:#999}input:focus{border-color:var(--fu-table-sticky-border)}::ng-deep .search-highlight{background-color:#fde047;padding:0 2px;border-radius:2px}::ng-deep .search-highlight.active{background-color:#fb7185!important}\n"], dependencies: [{ kind: "pipe", type: FuHighlightPipe, name: "fuHighlight" }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.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: i2.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i2.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "pipe", type: DatePipe, name: "date" }, { kind: "directive", type: FuStopPropagationDirective, selector: "[fuStopPropagation]" }] }); }
|
|
1697
|
+
}
|
|
1698
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.6", ngImport: i0, type: FuCellComponent, decorators: [{
|
|
1699
|
+
type: Component,
|
|
1700
|
+
args: [{ selector: 'fu-cell', imports: [FuHighlightPipe, FormsModule, DatePipe, FuStopPropagationDirective], template: "@if (editable() && editing()) {\r\n @switch (valueType()) {\r\n @case ('number') {\r\n <input\r\n type=\"number\"\r\n [(ngModel)]=\"value\"\r\n (ngModelChange)=\"numberChange($event)\"\r\n fuStopPropagation\r\n />\r\n }\r\n\r\n @case ('boolean') {\r\n <input\r\n type=\"checkbox\"\r\n [(ngModel)]=\"value\"\r\n (ngModelChange)=\"booleanChange($event)\"\r\n fuStopPropagation\r\n />\r\n }\r\n\r\n @case ('date') {\r\n <input\r\n type=\"date\"\r\n [(ngModel)]=\"dateValue\"\r\n (change)=\"dateChange($event)\"\r\n fuStopPropagation\r\n />\r\n }\r\n\r\n @case ('string') {\r\n <input\r\n type=\"text\"\r\n [(ngModel)]=\"value\"\r\n (ngModelChange)=\"textChange($event)\"\r\n fuStopPropagation\r\n />\r\n }\r\n\r\n @case ('text') {\r\n <input\r\n type=\"text\"\r\n [(ngModel)]=\"value\"\r\n (ngModelChange)=\"textChange($event)\"\r\n fuStopPropagation\r\n />\r\n }\r\n }\r\n} @else {\r\n <div>\r\n @switch (valueType()) {\r\n @case ('date') {\r\n <span>{{ value() | date }}</span>\r\n }\r\n\r\n @case ('text') {\r\n <span\r\n [innerHTML]=\"value() | fuHighlight: searchTerm() : activeMatch()\"\r\n >{{ value() }}</span\r\n >\r\n }\r\n\r\n @case ('number') {\r\n <span\r\n [innerHTML]=\"value() | fuHighlight: searchTerm() : activeMatch()\"\r\n >{{ value() }}</span\r\n >\r\n }\r\n\r\n @case ('boolean') {\r\n <input type=\"checkbox\" [disabled]=\"true\" [checked]=\"value()\" />\r\n }\r\n\r\n @default {\r\n <span\r\n [innerHTML]=\"value() | fuHighlight: searchTerm() : activeMatch()\"\r\n >{{ value() }}</span\r\n >\r\n }\r\n }\r\n </div>\r\n}\r\n", styles: ["input{padding:6px 8px;font-size:14px;line-height:1.4;border:1px solid var(--fu-table-border);border-radius:var(--fu-border-radius);background-color:var(--fu-table-input-bg);color:var(--fu-table-text);outline:none;transition:border-color .15s,box-shadow .15s}input[type=number],input[type=text]{width:100%}input::placeholder{color:#999}input:focus{border-color:var(--fu-table-sticky-border)}::ng-deep .search-highlight{background-color:#fde047;padding:0 2px;border-radius:2px}::ng-deep .search-highlight.active{background-color:#fb7185!important}\n"] }]
|
|
1701
|
+
}] });
|
|
1702
|
+
|
|
1703
|
+
class FuTableIntlService {
|
|
1704
|
+
constructor() {
|
|
1705
|
+
this.searchPlaceholder = signal('Search...');
|
|
1706
|
+
this.previous = signal('Previous');
|
|
1707
|
+
this.next = signal('Next');
|
|
1708
|
+
this.noEntries = signal('No entries');
|
|
1709
|
+
this.noData = signal('No data available');
|
|
1710
|
+
this.responsiveLabel = signal('Responsive');
|
|
1711
|
+
this.scrollableLabel = signal('Scrollable');
|
|
1712
|
+
this.lockHeaderLabel = signal('Lock header');
|
|
1713
|
+
this.lockLeftLabel = signal('Lock left column');
|
|
1714
|
+
this.lockRightLabel = signal('Lock right column');
|
|
1715
|
+
this.pageLabel = signal('Page');
|
|
1716
|
+
this.ofLabel = signal('of');
|
|
1717
|
+
this.entriesLabel = (first, last, total) => `${first} to ${last} of ${total} entries`;
|
|
1718
|
+
}
|
|
1719
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.6", ngImport: i0, type: FuTableIntlService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
1720
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.0.6", ngImport: i0, type: FuTableIntlService, providedIn: 'root' }); }
|
|
1721
|
+
}
|
|
1722
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.6", ngImport: i0, type: FuTableIntlService, decorators: [{
|
|
1723
|
+
type: Injectable,
|
|
1724
|
+
args: [{
|
|
1725
|
+
providedIn: 'root',
|
|
1726
|
+
}]
|
|
1727
|
+
}] });
|
|
1728
|
+
|
|
1729
|
+
class FuButtonDirective {
|
|
1730
|
+
constructor() {
|
|
1731
|
+
this.variant = input('default');
|
|
1732
|
+
this.severity = input(null);
|
|
1733
|
+
this.loading = input(false);
|
|
1734
|
+
this.disabled = input(false);
|
|
1735
|
+
}
|
|
1736
|
+
get classes() {
|
|
1737
|
+
return {
|
|
1738
|
+
'fu-button': true,
|
|
1739
|
+
[`fu-button--${this.variant()}`]: true,
|
|
1740
|
+
[`fu-button--${this.severity()}`]: this.severity(),
|
|
1741
|
+
'fu-button--loading': this.loading(),
|
|
1742
|
+
'fu-button--disabled': this.disabled(),
|
|
1743
|
+
};
|
|
1744
|
+
}
|
|
1745
|
+
get isDisabled() {
|
|
1746
|
+
return this.disabled() || this.loading() || null;
|
|
1747
|
+
}
|
|
1748
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.6", ngImport: i0, type: FuButtonDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
1749
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "19.0.6", type: FuButtonDirective, isStandalone: true, selector: "button[fuButton], a[fuButton]", inputs: { variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, severity: { classPropertyName: "severity", publicName: "severity", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "this.classes", "attr.disabled": "this.isDisabled" } }, ngImport: i0 }); }
|
|
1750
|
+
}
|
|
1751
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.6", ngImport: i0, type: FuButtonDirective, decorators: [{
|
|
1752
|
+
type: Directive,
|
|
1753
|
+
args: [{
|
|
1754
|
+
selector: 'button[fuButton], a[fuButton]',
|
|
1755
|
+
}]
|
|
1756
|
+
}], propDecorators: { classes: [{
|
|
1757
|
+
type: HostBinding,
|
|
1758
|
+
args: ['class']
|
|
1759
|
+
}], isDisabled: [{
|
|
1760
|
+
type: HostBinding,
|
|
1761
|
+
args: ['attr.disabled']
|
|
1762
|
+
}] } });
|
|
1763
|
+
|
|
1764
|
+
class FuTableComponent {
|
|
1765
|
+
constructor(injector) {
|
|
1766
|
+
this.injector = injector;
|
|
1767
|
+
// ========================================
|
|
1768
|
+
// Data Management
|
|
1769
|
+
// ========================================
|
|
1770
|
+
/**
|
|
1771
|
+
* The table's data source.
|
|
1772
|
+
* Required input that provides the array of objects to display in the fu-table.
|
|
1773
|
+
*
|
|
1774
|
+
* @example
|
|
1775
|
+
* ```html
|
|
1776
|
+
* <fu-table [dataSource]="users">
|
|
1777
|
+
* ```
|
|
1778
|
+
*/
|
|
1779
|
+
this.dataSource = model.required();
|
|
1780
|
+
/**
|
|
1781
|
+
* Total number of items available for server-side pagination.
|
|
1782
|
+
* When this value exceeds `dataSource.length`, fu-table operates in server-side mode
|
|
1783
|
+
* and expects the parent to handle pagination, sorting, and filtering.
|
|
1784
|
+
*
|
|
1785
|
+
* @default 0
|
|
1786
|
+
* @example
|
|
1787
|
+
* ```html
|
|
1788
|
+
* <fu-table [dataSource]="users" [totalItems]="1000">
|
|
1789
|
+
* ```
|
|
1790
|
+
*/
|
|
1791
|
+
this.totalItems = input(0);
|
|
1792
|
+
// ========================================
|
|
1793
|
+
// Persistence & Configuration
|
|
1794
|
+
// ========================================
|
|
1795
|
+
/**
|
|
1796
|
+
* Key for persisting table state (pagination, sorting, columns, UI settings) to localStorage.
|
|
1797
|
+
* When provided, fu-table will automatically save and restore its state across sessions.
|
|
1798
|
+
*
|
|
1799
|
+
* @default undefined
|
|
1800
|
+
* @example
|
|
1801
|
+
* ```html
|
|
1802
|
+
* <fu-table [localStorageKey]="'my-users-table'">
|
|
1803
|
+
* ```
|
|
1804
|
+
*/
|
|
1805
|
+
this.localStorageKey = input();
|
|
1806
|
+
/**
|
|
1807
|
+
* External column visibility configuration.
|
|
1808
|
+
* When provided, this overrides the `visible` property on individual column definitions.
|
|
1809
|
+
* Useful for programmatically controlling which columns are displayed.
|
|
1810
|
+
*
|
|
1811
|
+
* @default []
|
|
1812
|
+
* @example
|
|
1813
|
+
* ```typescript
|
|
1814
|
+
* columnsConfig = [
|
|
1815
|
+
* { field: 'name', visible: true },
|
|
1816
|
+
* { field: 'email', visible: false }
|
|
1817
|
+
* ];
|
|
1818
|
+
* ```
|
|
1819
|
+
* ```html
|
|
1820
|
+
* <fu-table [columnsConfig]="columnsConfig">
|
|
1821
|
+
* ```
|
|
1822
|
+
*/
|
|
1823
|
+
this.columnsConfig = input([]);
|
|
1824
|
+
// ========================================
|
|
1825
|
+
// Feature Toggles
|
|
1826
|
+
// ========================================
|
|
1827
|
+
/**
|
|
1828
|
+
* Enables the configuration sidebar panel.
|
|
1829
|
+
* When true, displays a button to open a sidebar for managing column order,
|
|
1830
|
+
* visibility, and layout settings.
|
|
1831
|
+
*
|
|
1832
|
+
* @default false
|
|
1833
|
+
* @example
|
|
1834
|
+
* ```html
|
|
1835
|
+
* <fu-table [configPanel]="true">
|
|
1836
|
+
* ```
|
|
1837
|
+
*/
|
|
1838
|
+
this.configPanel = input(false);
|
|
1839
|
+
/**
|
|
1840
|
+
* Enables the filter/search panel.
|
|
1841
|
+
* When true, displays a search input that filters rows based on column values.
|
|
1842
|
+
*
|
|
1843
|
+
* @default false
|
|
1844
|
+
* @example
|
|
1845
|
+
* ```html
|
|
1846
|
+
* <fu-table [filterPanel]="true">
|
|
1847
|
+
* ```
|
|
1848
|
+
*/
|
|
1849
|
+
this.filterPanel = input(false);
|
|
1850
|
+
/**
|
|
1851
|
+
* Enables pagination controls.
|
|
1852
|
+
* When true, displays page navigation and items-per-page selector.
|
|
1853
|
+
*
|
|
1854
|
+
* @default true
|
|
1855
|
+
* @example
|
|
1856
|
+
* ```html
|
|
1857
|
+
* <fu-table [paginator]="false">
|
|
1858
|
+
* ```
|
|
1859
|
+
*/
|
|
1860
|
+
this.paginator = input(true);
|
|
1861
|
+
/**
|
|
1862
|
+
* Loading state indicator.
|
|
1863
|
+
* When true, displays loading skeletons instead of table data.
|
|
1864
|
+
* Useful for showing loading state during async operations.
|
|
1865
|
+
*
|
|
1866
|
+
* @default false
|
|
1867
|
+
* @example
|
|
1868
|
+
* ```html
|
|
1869
|
+
* <fu-table [loading]="isLoadingData">
|
|
1870
|
+
* ```
|
|
1871
|
+
*/
|
|
1872
|
+
this.loading = input(false);
|
|
1873
|
+
/**
|
|
1874
|
+
* Disables pagination controls while keeping them visible.
|
|
1875
|
+
* Useful for temporarily preventing pagination interaction during operations.
|
|
1876
|
+
*
|
|
1877
|
+
* @default false
|
|
1878
|
+
* @example
|
|
1879
|
+
* ```html
|
|
1880
|
+
* <fu-table [disablePaginator]="isSaving">
|
|
1881
|
+
* ```
|
|
1882
|
+
*/
|
|
1883
|
+
this.disablePaginator = input(false);
|
|
1884
|
+
/**
|
|
1885
|
+
* Trigger to reset pagination to first page.
|
|
1886
|
+
* When this input value changes, the fu-table resets `pageIndex` to 0
|
|
1887
|
+
* and emits a new query event. Useful for resetting pagination after filter changes.
|
|
1888
|
+
*
|
|
1889
|
+
* @example
|
|
1890
|
+
* ```typescript
|
|
1891
|
+
* resetTrigger = 0;
|
|
1892
|
+
* onFilterChange() {
|
|
1893
|
+
* this.resetTrigger++;
|
|
1894
|
+
* }
|
|
1895
|
+
* ```
|
|
1896
|
+
* ```html
|
|
1897
|
+
* <fu-table [resetQuery]="resetTrigger">
|
|
1898
|
+
* ```
|
|
1899
|
+
*/
|
|
1900
|
+
this.resetQuery = input();
|
|
1901
|
+
// ========================================
|
|
1902
|
+
// Layout & Appearance
|
|
1903
|
+
// ========================================
|
|
1904
|
+
/**
|
|
1905
|
+
* Enables responsive mode.
|
|
1906
|
+
* When true, fu-table adapts its layout for mobile devices by stacking cell content.
|
|
1907
|
+
*
|
|
1908
|
+
* @default true
|
|
1909
|
+
* @example
|
|
1910
|
+
* ```html
|
|
1911
|
+
* <fu-table [responsive]="false">
|
|
1912
|
+
* ```
|
|
1913
|
+
*/
|
|
1914
|
+
this.responsive = input(true);
|
|
1915
|
+
/**
|
|
1916
|
+
* Fixed scroll height in pixels.
|
|
1917
|
+
* When provided, sets a maximum height for the fu-table scroll container.
|
|
1918
|
+
* Useful when `scrollable` is true.
|
|
1919
|
+
*
|
|
1920
|
+
* @example
|
|
1921
|
+
* ```html
|
|
1922
|
+
* <fu-table [scrollable]="true" [scrollHeight]="400">
|
|
1923
|
+
* ```
|
|
1924
|
+
*/
|
|
1925
|
+
this.scrollHeight = input();
|
|
1926
|
+
/**
|
|
1927
|
+
* Enables scrollable container with fixed height.
|
|
1928
|
+
* When true, the fu-table body becomes scrollable while headers remain fixed.
|
|
1929
|
+
*
|
|
1930
|
+
* @default false
|
|
1931
|
+
* @example
|
|
1932
|
+
* ```html
|
|
1933
|
+
* <fu-table [scrollable]="true" [scrollHeight]="500">
|
|
1934
|
+
* ```
|
|
1935
|
+
*/
|
|
1936
|
+
this.scrollable = input(false);
|
|
1937
|
+
/**
|
|
1938
|
+
* Makes the header row sticky when scrolling.
|
|
1939
|
+
* Requires `scrollable` to be true to take effect.
|
|
1940
|
+
*
|
|
1941
|
+
* @default false
|
|
1942
|
+
* @example
|
|
1943
|
+
* ```html
|
|
1944
|
+
* <fu-table [scrollable]="true" [stickyHeader]="true">
|
|
1945
|
+
* ```
|
|
1946
|
+
*/
|
|
1947
|
+
this.stickyHeader = input(false);
|
|
1948
|
+
/**
|
|
1949
|
+
* Makes the first column sticky when scrolling horizontally.
|
|
1950
|
+
* Useful for keeping identifier columns visible while scrolling.
|
|
1951
|
+
*
|
|
1952
|
+
* @default false
|
|
1953
|
+
* @example
|
|
1954
|
+
* ```html
|
|
1955
|
+
* <fu-table [stickyFirstColumn]="true">
|
|
1956
|
+
* ```
|
|
1957
|
+
*/
|
|
1958
|
+
this.stickyFirstColumn = input(false);
|
|
1959
|
+
/**
|
|
1960
|
+
* Makes the last column sticky when scrolling horizontally.
|
|
1961
|
+
* Useful for keeping action columns visible while scrolling.
|
|
1962
|
+
*
|
|
1963
|
+
* @default false
|
|
1964
|
+
* @example
|
|
1965
|
+
* ```html
|
|
1966
|
+
* <fu-table [stickyLastColumn]="true">
|
|
1967
|
+
* ```
|
|
1968
|
+
*/
|
|
1969
|
+
this.stickyLastColumn = input(false);
|
|
1970
|
+
// ========================================
|
|
1971
|
+
// Row State Management
|
|
1972
|
+
// ========================================
|
|
1973
|
+
/**
|
|
1974
|
+
* Current row operation state for edit/save operations.
|
|
1975
|
+
* Used to track the state of row edit submissions (idle, pending, success, error).
|
|
1976
|
+
* The fu-table will display appropriate visual feedback based on this state.
|
|
1977
|
+
*
|
|
1978
|
+
* @default 'idle'
|
|
1979
|
+
* @example
|
|
1980
|
+
* ```html
|
|
1981
|
+
* <fu-table [rowState]="saveState" (rowEditSubmit)="saveRow($event)">
|
|
1982
|
+
* ```
|
|
1983
|
+
*/
|
|
1984
|
+
this.rowState = input('idle');
|
|
1985
|
+
this.rowClass = input(null);
|
|
1986
|
+
this.intl = inject(FuTableIntlService);
|
|
1987
|
+
this.responsiveConfig = linkedSignal(this.responsive);
|
|
1988
|
+
this.scrollHeightConfig = linkedSignal(this.scrollHeight);
|
|
1989
|
+
this.scrollableConfig = linkedSignal(this.scrollable);
|
|
1990
|
+
this.stickyHeaderConfig = linkedSignal(this.stickyHeader);
|
|
1991
|
+
this.stickyFirstColumnConfig = linkedSignal(this.stickyFirstColumn);
|
|
1992
|
+
this.stickyLastColumnConfig = linkedSignal(this.stickyLastColumn);
|
|
1993
|
+
this.sidebarTemplate = contentChild(FuTableSidebarTemplateDirective, {
|
|
1994
|
+
read: TemplateRef,
|
|
1995
|
+
});
|
|
1996
|
+
this.noDataTemplate = contentChild(FuTableNoDataTemplateDirective, {
|
|
1997
|
+
read: TemplateRef,
|
|
1998
|
+
});
|
|
1999
|
+
this.expandRowTemplate = contentChild(FuTableExpandRowTemplateDirective, {
|
|
2000
|
+
read: TemplateRef,
|
|
2001
|
+
});
|
|
2002
|
+
this.rowContextMenuTemplate = contentChild(FuRowContextMenuTemplateDirective, {
|
|
2003
|
+
read: TemplateRef,
|
|
2004
|
+
});
|
|
2005
|
+
this.scrollContainer = viewChild('scrollContainer');
|
|
2006
|
+
/**
|
|
2007
|
+
* Emitted when fu-table requests new data in server-side mode.
|
|
2008
|
+
* Fires on page change, page size change, and sort changes.
|
|
2009
|
+
*
|
|
2010
|
+
* Payload: `FuTableQuery` with `pageIndex`, `pageSize`, optional `sortField` and `sortDirection`.
|
|
2011
|
+
* Note: `searchTerm` is persisted internally but not included in this event.
|
|
2012
|
+
*
|
|
2013
|
+
* @example
|
|
2014
|
+
* ```html
|
|
2015
|
+
* <fu-table
|
|
2016
|
+
* [dataSource]="rows"
|
|
2017
|
+
* [totalItems]="total"
|
|
2018
|
+
* [paginator]="true"
|
|
2019
|
+
* (queryChanged)="load($event)">
|
|
2020
|
+
* </fu-table>
|
|
2021
|
+
* ```
|
|
2022
|
+
*/
|
|
2023
|
+
this.queryChanged = output();
|
|
2024
|
+
/**
|
|
2025
|
+
* Emitted when an editable cell value changes.
|
|
2026
|
+
*
|
|
2027
|
+
* Payload: an object with a single entry where the key is the column field
|
|
2028
|
+
* path (dot notation supported) and the value is the new cell value.
|
|
2029
|
+
* Example payload: `{ "user.name": "Alice" }`.
|
|
2030
|
+
*
|
|
2031
|
+
* @example
|
|
2032
|
+
* ```html
|
|
2033
|
+
* <fu-table (cellValueEdited)="onCellEdit($event)"></fu-table>
|
|
2034
|
+
* ```
|
|
2035
|
+
*/
|
|
2036
|
+
this.cellValueEdited = output();
|
|
2037
|
+
/**
|
|
2038
|
+
* Emitted when columns are reordered or their visibility is toggled via the UI.
|
|
2039
|
+
*
|
|
2040
|
+
* Payload: the current column configuration and order as `FuColumnConfig[]`.
|
|
2041
|
+
* Example payload: `[{ field: 'name', visible: true }, { field: 'email', visible: false }]`.
|
|
2042
|
+
*
|
|
2043
|
+
* @example
|
|
2044
|
+
* ```html
|
|
2045
|
+
* <fu-table [configPanel]="true" (columnsChanged)="persistColumns($event)"></fu-table>
|
|
2046
|
+
* ```
|
|
2047
|
+
*/
|
|
2048
|
+
this.columnsChanged = output();
|
|
2049
|
+
/**
|
|
2050
|
+
* Emitted when an inline row edit is submitted.
|
|
2051
|
+
*
|
|
2052
|
+
* You should persist the changes and then set `[rowState]` to `'success'` or `'error'`
|
|
2053
|
+
* to reflect the result. The component will display feedback accordingly.
|
|
2054
|
+
*
|
|
2055
|
+
* Payload: the edited row object of type `T`.
|
|
2056
|
+
*
|
|
2057
|
+
* @example
|
|
2058
|
+
* ```html
|
|
2059
|
+
* <fu-table [rowState]="saveState" (rowEditSubmit)="saveRow($event)"></fu-table>
|
|
2060
|
+
* ```
|
|
2061
|
+
*/
|
|
2062
|
+
this.rowEditSubmit = output();
|
|
2063
|
+
/**
|
|
2064
|
+
* Emitted when a row is clicked.
|
|
2065
|
+
*
|
|
2066
|
+
* Payload: the clicked row object of type `T`.
|
|
2067
|
+
*
|
|
2068
|
+
* @example
|
|
2069
|
+
* ```html
|
|
2070
|
+
* <fu-table (rowClicked)="selectRow($event)"></fu-table>
|
|
2071
|
+
* ```
|
|
2072
|
+
*/
|
|
2073
|
+
this.rowClicked = output();
|
|
2074
|
+
this.columns = signal([]);
|
|
2075
|
+
this.searchTerm = signal('');
|
|
2076
|
+
this.sidebarOpen = signal(false);
|
|
2077
|
+
this.expandIndex = signal(null);
|
|
2078
|
+
this.originalData = signal([]);
|
|
2079
|
+
this.pageIndex = signal(0);
|
|
2080
|
+
this.pageSize = signal(5);
|
|
2081
|
+
this.pageSizeOptions = signal([5, 10, 20, 50]);
|
|
2082
|
+
this.editingRowIndex = signal(null);
|
|
2083
|
+
this.pendingSaveRowIndex = signal(null);
|
|
2084
|
+
this.originalRowSnapshot = signal(null);
|
|
2085
|
+
this.rowStates = new Map();
|
|
2086
|
+
this.rowLoading = new Set();
|
|
2087
|
+
this.activeMatchIndex = signal(0);
|
|
2088
|
+
this.visibleCount = computed(() => this.columns().filter(c => c.visible()).length);
|
|
2089
|
+
this.initialLoading = computed(() => this.loading() && this.dataSource().length === 0);
|
|
2090
|
+
this.isServerData = computed(() => this.totalItems() > this.dataSource().length);
|
|
2091
|
+
this.entriesLabel = computed(() => {
|
|
2092
|
+
const total = this.actualTotalItems();
|
|
2093
|
+
if (!total)
|
|
2094
|
+
return this.intl.noEntries();
|
|
2095
|
+
const start = this.pageIndex() * this.pageSize() + 1;
|
|
2096
|
+
const end = Math.min((this.pageIndex() + 1) * this.pageSize(), total);
|
|
2097
|
+
return this.intl.entriesLabel(start, end, total);
|
|
2098
|
+
});
|
|
2099
|
+
this.filteredData = computed(() => {
|
|
2100
|
+
if (!this.searchTerm())
|
|
2101
|
+
return this.dataSource();
|
|
2102
|
+
return this.dataSource().filter(row => this.columns().some(col => {
|
|
2103
|
+
const value = this.getCellValue(row, col.field());
|
|
2104
|
+
return (value != null &&
|
|
2105
|
+
String(value).toLowerCase().includes(this.searchTerm()));
|
|
2106
|
+
}));
|
|
2107
|
+
});
|
|
2108
|
+
this.visibleRows = computed(() => {
|
|
2109
|
+
const data = this.filteredData();
|
|
2110
|
+
if (this.isServerData()) {
|
|
2111
|
+
return data;
|
|
2112
|
+
}
|
|
2113
|
+
const start = this.pageIndex() * this.pageSize();
|
|
2114
|
+
return data.slice(start, start + this.pageSize());
|
|
2115
|
+
});
|
|
2116
|
+
this.actualTotalItems = computed(() => {
|
|
2117
|
+
if (this.isServerData()) {
|
|
2118
|
+
return this.totalItems();
|
|
2119
|
+
}
|
|
2120
|
+
return this.filteredData().length;
|
|
2121
|
+
});
|
|
2122
|
+
this.skeletonRowCount = computed(() => this.pageSize());
|
|
2123
|
+
this.skeletonColumnCount = computed(() => this.visibleColumns().length + (this.expandRowTemplate() ? 1 : 0));
|
|
2124
|
+
this.totalPages = computed(() => Math.ceil(this.actualTotalItems() / this.pageSize()));
|
|
2125
|
+
this.visibleColumns = computed(() => this.columns().filter(col => col.visible()));
|
|
2126
|
+
this.activeSort = computed(() => {
|
|
2127
|
+
const col = this.columns().find(c => c.sortDirection() !== null);
|
|
2128
|
+
if (!col)
|
|
2129
|
+
return null;
|
|
2130
|
+
return {
|
|
2131
|
+
field: col.field(),
|
|
2132
|
+
sortKey: col.sortField() ?? col.field(),
|
|
2133
|
+
direction: col.sortDirection(),
|
|
2134
|
+
};
|
|
2135
|
+
});
|
|
2136
|
+
this.searchMatches = computed(() => {
|
|
2137
|
+
const term = this.searchTerm();
|
|
2138
|
+
if (!term)
|
|
2139
|
+
return [];
|
|
2140
|
+
const rows = this.visibleRows();
|
|
2141
|
+
const columns = this.columns();
|
|
2142
|
+
const matches = [];
|
|
2143
|
+
rows.forEach((row, rowIndex) => {
|
|
2144
|
+
columns.forEach(col => {
|
|
2145
|
+
const value = this.getCellValue(row, col.field());
|
|
2146
|
+
if (value == null)
|
|
2147
|
+
return;
|
|
2148
|
+
if (String(value).toLowerCase().includes(term)) {
|
|
2149
|
+
matches.push({ rowIndex, column: col });
|
|
2150
|
+
}
|
|
2151
|
+
});
|
|
2152
|
+
});
|
|
2153
|
+
return matches;
|
|
2154
|
+
});
|
|
2155
|
+
this.activeSidebarPanel = signal(null);
|
|
2156
|
+
this.isSidebarOpen = computed(() => this.activeSidebarPanel() !== null);
|
|
2157
|
+
this.isColumnsOpen = computed(() => this.activeSidebarPanel() === 'columns');
|
|
2158
|
+
this.isLayoutOpen = computed(() => this.activeSidebarPanel() === 'layout');
|
|
2159
|
+
this.isCustomOpen = computed(() => this.activeSidebarPanel() === 'custom');
|
|
2160
|
+
this.persist = afterRenderEffect(() => {
|
|
2161
|
+
this.resetLayoutConfig();
|
|
2162
|
+
this.scrollableConfig();
|
|
2163
|
+
this.stickyHeaderConfig();
|
|
2164
|
+
this.stickyFirstColumnConfig();
|
|
2165
|
+
this.stickyLastColumnConfig();
|
|
2166
|
+
this.saveState();
|
|
2167
|
+
});
|
|
2168
|
+
}
|
|
2169
|
+
ngOnChanges(changes) {
|
|
2170
|
+
if (changes['dataSource']) {
|
|
2171
|
+
this.originalData.set([...this.dataSource()]);
|
|
2172
|
+
// this.pageIndex.set(0);
|
|
2173
|
+
}
|
|
2174
|
+
if (changes['columnsConfig'] && !changes['columnsConfig'].firstChange) {
|
|
2175
|
+
this.applyColumnsConfig();
|
|
2176
|
+
}
|
|
2177
|
+
if (changes['rowState']) {
|
|
2178
|
+
this.handleRowSaveResult();
|
|
2179
|
+
}
|
|
2180
|
+
if (!changes['loading'].firstChange &&
|
|
2181
|
+
changes['loading'].currentValue === false &&
|
|
2182
|
+
!changes['rowState']) {
|
|
2183
|
+
this.rowStates.clear();
|
|
2184
|
+
}
|
|
2185
|
+
if (changes['resetQuery'] && !changes['resetQuery'].firstChange) {
|
|
2186
|
+
this.pageIndex.set(0);
|
|
2187
|
+
this.emitQueryIfNeeded();
|
|
2188
|
+
}
|
|
2189
|
+
}
|
|
2190
|
+
ngAfterContentInit() {
|
|
2191
|
+
const persisted = this.loadState();
|
|
2192
|
+
// 1️⃣ restore query
|
|
2193
|
+
const query = persisted?.query;
|
|
2194
|
+
if (query) {
|
|
2195
|
+
this.pageIndex.set(query.pageIndex);
|
|
2196
|
+
this.pageSize.set(query.pageSize);
|
|
2197
|
+
this.searchTerm.set(query.searchTerm ?? '');
|
|
2198
|
+
this.restoreSort(query.sortField, query.sortDirection);
|
|
2199
|
+
this.applyClientSort();
|
|
2200
|
+
}
|
|
2201
|
+
// 2️⃣ restore columns
|
|
2202
|
+
const colsConfig = this.columnsConfig().length
|
|
2203
|
+
? this.columnsConfig()
|
|
2204
|
+
: persisted?.columns;
|
|
2205
|
+
if (colsConfig) {
|
|
2206
|
+
this.applyColumnsConfig(colsConfig);
|
|
2207
|
+
}
|
|
2208
|
+
// 🔑 ui settings
|
|
2209
|
+
if (persisted?.ui) {
|
|
2210
|
+
this.responsiveConfig.set(persisted.ui.responsive);
|
|
2211
|
+
this.scrollableConfig.set(persisted.ui.scrollable);
|
|
2212
|
+
this.stickyHeaderConfig.set(persisted.ui.stickyHeader);
|
|
2213
|
+
this.stickyFirstColumnConfig.set(persisted.ui.stickyFirstColumn);
|
|
2214
|
+
this.stickyLastColumnConfig.set(persisted.ui.stickyLastColumn);
|
|
2215
|
+
}
|
|
2216
|
+
this.emitQueryIfNeeded();
|
|
2217
|
+
}
|
|
2218
|
+
resetLayoutConfig() {
|
|
2219
|
+
if (this.scrollableConfig() === false) {
|
|
2220
|
+
this.scrollableConfig.set(false);
|
|
2221
|
+
this.stickyHeaderConfig.set(false);
|
|
2222
|
+
this.stickyFirstColumnConfig.set(false);
|
|
2223
|
+
this.stickyLastColumnConfig.set(false);
|
|
2224
|
+
}
|
|
2225
|
+
}
|
|
2226
|
+
loadState() {
|
|
2227
|
+
if (!this.localStorageKey())
|
|
2228
|
+
return null;
|
|
2229
|
+
try {
|
|
2230
|
+
const raw = localStorage.getItem(this.localStorageKey());
|
|
2231
|
+
return raw ? JSON.parse(raw) : null;
|
|
2232
|
+
}
|
|
2233
|
+
catch {
|
|
2234
|
+
return null;
|
|
2235
|
+
}
|
|
2236
|
+
}
|
|
2237
|
+
saveState() {
|
|
2238
|
+
if (!this.localStorageKey())
|
|
2239
|
+
return;
|
|
2240
|
+
const state = {
|
|
2241
|
+
query: {
|
|
2242
|
+
pageIndex: this.pageIndex(),
|
|
2243
|
+
pageSize: this.pageSize(),
|
|
2244
|
+
sortField: this.activeSort()?.field,
|
|
2245
|
+
sortDirection: this.activeSort()?.direction,
|
|
2246
|
+
searchTerm: this.searchTerm(),
|
|
2247
|
+
},
|
|
2248
|
+
columns: this.columns().map(col => ({
|
|
2249
|
+
field: col.field(),
|
|
2250
|
+
visible: col.visible(),
|
|
2251
|
+
})),
|
|
2252
|
+
ui: {
|
|
2253
|
+
responsive: this.responsiveConfig(),
|
|
2254
|
+
scrollable: this.scrollableConfig(),
|
|
2255
|
+
stickyHeader: this.stickyHeaderConfig(),
|
|
2256
|
+
stickyFirstColumn: this.stickyFirstColumnConfig(),
|
|
2257
|
+
stickyLastColumn: this.stickyLastColumnConfig(),
|
|
2258
|
+
},
|
|
2259
|
+
};
|
|
2260
|
+
localStorage.setItem(this.localStorageKey(), JSON.stringify(state));
|
|
2261
|
+
}
|
|
2262
|
+
toggleColumnsPanel() {
|
|
2263
|
+
this.activeSidebarPanel.update(p => (p === 'columns' ? null : 'columns'));
|
|
2264
|
+
}
|
|
2265
|
+
toggleLayoutPanel() {
|
|
2266
|
+
this.activeSidebarPanel.update(p => (p === 'layout' ? null : 'layout'));
|
|
2267
|
+
}
|
|
2268
|
+
toggleCustomPanel() {
|
|
2269
|
+
this.activeSidebarPanel.update(p => (p === 'custom' ? null : 'custom'));
|
|
2270
|
+
}
|
|
2271
|
+
applyColumnsConfig(config = this.columnsConfig()) {
|
|
2272
|
+
if (!config.length)
|
|
2273
|
+
return;
|
|
2274
|
+
const byField = new Map(this.columns().map(c => [c.field(), c]));
|
|
2275
|
+
const reordered = [];
|
|
2276
|
+
for (const cfg of config) {
|
|
2277
|
+
const col = byField.get(cfg.field);
|
|
2278
|
+
if (!col)
|
|
2279
|
+
continue;
|
|
2280
|
+
col.visible.set(cfg.visible !== false); // default true
|
|
2281
|
+
reordered.push(col);
|
|
2282
|
+
byField.delete(cfg.field);
|
|
2283
|
+
}
|
|
2284
|
+
// columns not mentioned in config → visible by default
|
|
2285
|
+
for (const col of byField.values()) {
|
|
2286
|
+
col.visible.set(true);
|
|
2287
|
+
reordered.push(col);
|
|
2288
|
+
}
|
|
2289
|
+
this.columns.set(reordered);
|
|
2290
|
+
this.saveState();
|
|
2291
|
+
}
|
|
2292
|
+
toggleSidebar() {
|
|
2293
|
+
this.sidebarOpen.update(v => !v);
|
|
2294
|
+
}
|
|
2295
|
+
startEditRow(index, event) {
|
|
2296
|
+
event?.stopPropagation();
|
|
2297
|
+
this.resetEditRow();
|
|
2298
|
+
this.editingRowIndex.set(index);
|
|
2299
|
+
this.originalRowSnapshot.set(structuredClone(this.visibleRows()[index]));
|
|
2300
|
+
this.rowStates.set(index, 'pending');
|
|
2301
|
+
}
|
|
2302
|
+
saveEditRow(event) {
|
|
2303
|
+
event?.stopPropagation();
|
|
2304
|
+
if (this.editingRowIndex() === null)
|
|
2305
|
+
return;
|
|
2306
|
+
const row = this.visibleRows()[this.editingRowIndex()];
|
|
2307
|
+
this.pendingSaveRowIndex.set(this.editingRowIndex());
|
|
2308
|
+
this.editingRowIndex.set(null);
|
|
2309
|
+
this.rowLoading.add(this.pendingSaveRowIndex());
|
|
2310
|
+
this.rowEditSubmit.emit(row);
|
|
2311
|
+
}
|
|
2312
|
+
cancelEditRow(event) {
|
|
2313
|
+
event?.stopPropagation();
|
|
2314
|
+
if (this.editingRowIndex() === null || !this.originalRowSnapshot())
|
|
2315
|
+
return;
|
|
2316
|
+
Object.assign(this.visibleRows()[this.editingRowIndex()], this.originalRowSnapshot());
|
|
2317
|
+
// this.filteredData.update(data => {
|
|
2318
|
+
// const copy = [...data];
|
|
2319
|
+
// copy[this.editingRowIndex!] = {
|
|
2320
|
+
// ...copy[this.editingRowIndex!],
|
|
2321
|
+
// ...this.originalRowSnapshot,
|
|
2322
|
+
// };
|
|
2323
|
+
// return copy;
|
|
2324
|
+
// });
|
|
2325
|
+
this.rowStates.set(this.editingRowIndex(), 'idle');
|
|
2326
|
+
this.editingRowIndex.set(null);
|
|
2327
|
+
this.originalRowSnapshot.set(null);
|
|
2328
|
+
}
|
|
2329
|
+
resetEditRow() {
|
|
2330
|
+
if (this.editingRowIndex() === null)
|
|
2331
|
+
return;
|
|
2332
|
+
this.pendingSaveRowIndex.set(null);
|
|
2333
|
+
this.editingRowIndex.set(null);
|
|
2334
|
+
this.originalRowSnapshot.set(null);
|
|
2335
|
+
this.rowStates.clear();
|
|
2336
|
+
}
|
|
2337
|
+
handleRowSaveResult() {
|
|
2338
|
+
const result = this.rowState();
|
|
2339
|
+
if (this.pendingSaveRowIndex() === null)
|
|
2340
|
+
return;
|
|
2341
|
+
const row = this.visibleRows()[this.pendingSaveRowIndex()];
|
|
2342
|
+
this.rowLoading.delete(this.pendingSaveRowIndex());
|
|
2343
|
+
if (result === 'success') {
|
|
2344
|
+
this.rowStates.set(this.pendingSaveRowIndex(), 'success');
|
|
2345
|
+
this.originalRowSnapshot.set(null);
|
|
2346
|
+
}
|
|
2347
|
+
else {
|
|
2348
|
+
Object.assign(row, this.originalRowSnapshot());
|
|
2349
|
+
this.rowStates.set(this.pendingSaveRowIndex(), 'error');
|
|
2350
|
+
this.originalRowSnapshot.set(null);
|
|
2351
|
+
}
|
|
2352
|
+
this.pendingSaveRowIndex.set(null);
|
|
2353
|
+
}
|
|
2354
|
+
onRowClick(row, event) {
|
|
2355
|
+
event.stopPropagation();
|
|
2356
|
+
this.rowClicked.emit(row);
|
|
2357
|
+
}
|
|
2358
|
+
onEditAnimationEnd() {
|
|
2359
|
+
this.rowStates.clear();
|
|
2360
|
+
}
|
|
2361
|
+
getCellValue(row, field) {
|
|
2362
|
+
if (!field)
|
|
2363
|
+
return null;
|
|
2364
|
+
return field
|
|
2365
|
+
.split('.')
|
|
2366
|
+
.reduce((acc, key) => acc?.[key], row);
|
|
2367
|
+
}
|
|
2368
|
+
toggleExpand(index, event) {
|
|
2369
|
+
event?.stopPropagation();
|
|
2370
|
+
this.expandIndex.update(i => (i === index ? null : index));
|
|
2371
|
+
}
|
|
2372
|
+
onSort(col, event) {
|
|
2373
|
+
event?.stopPropagation();
|
|
2374
|
+
this.activeMatchIndex.set(0);
|
|
2375
|
+
if (!col.sortable())
|
|
2376
|
+
return;
|
|
2377
|
+
col.toggleSort();
|
|
2378
|
+
this.columns.update(cols => cols.map(c => {
|
|
2379
|
+
if (c.field() !== col.field()) {
|
|
2380
|
+
c.sortDirection.set(null);
|
|
2381
|
+
}
|
|
2382
|
+
return c;
|
|
2383
|
+
}));
|
|
2384
|
+
this.pageIndex.set(0);
|
|
2385
|
+
if (this.isServerData()) {
|
|
2386
|
+
this.emitQueryIfNeeded();
|
|
2387
|
+
}
|
|
2388
|
+
else {
|
|
2389
|
+
this.applyClientSort();
|
|
2390
|
+
}
|
|
2391
|
+
this.resetEditRow();
|
|
2392
|
+
}
|
|
2393
|
+
restoreOriginalOrder() {
|
|
2394
|
+
this.dataSource.set([...this.originalData()]);
|
|
2395
|
+
}
|
|
2396
|
+
applyClientSort() {
|
|
2397
|
+
const sort = this.activeSort();
|
|
2398
|
+
if (!sort) {
|
|
2399
|
+
this.restoreOriginalOrder();
|
|
2400
|
+
this.saveState();
|
|
2401
|
+
return;
|
|
2402
|
+
}
|
|
2403
|
+
const { field, direction } = sort;
|
|
2404
|
+
const dir = direction === 'asc' ? 1 : -1;
|
|
2405
|
+
this.dataSource.update(data => {
|
|
2406
|
+
const sorted = [...data].sort((a, b) => {
|
|
2407
|
+
const av = this.getCellValue(a, field);
|
|
2408
|
+
const bv = this.getCellValue(b, field);
|
|
2409
|
+
return this.compareValues(av, bv) * dir;
|
|
2410
|
+
});
|
|
2411
|
+
return sorted;
|
|
2412
|
+
});
|
|
2413
|
+
this.saveState();
|
|
2414
|
+
}
|
|
2415
|
+
compareValues(a, b) {
|
|
2416
|
+
if (a == null && b == null)
|
|
2417
|
+
return 0;
|
|
2418
|
+
if (a == null)
|
|
2419
|
+
return -1;
|
|
2420
|
+
if (b == null)
|
|
2421
|
+
return 1;
|
|
2422
|
+
if (typeof a === 'number' && typeof b === 'number') {
|
|
2423
|
+
return a - b;
|
|
2424
|
+
}
|
|
2425
|
+
return String(a).localeCompare(String(b));
|
|
2426
|
+
}
|
|
2427
|
+
emitQueryIfNeeded() {
|
|
2428
|
+
this.queryChanged.emit({
|
|
2429
|
+
pageIndex: this.pageIndex(),
|
|
2430
|
+
pageSize: this.pageSize(),
|
|
2431
|
+
sortField: this.activeSort()?.sortKey,
|
|
2432
|
+
sortDirection: this.activeSort()?.direction,
|
|
2433
|
+
});
|
|
2434
|
+
this.saveState();
|
|
2435
|
+
}
|
|
2436
|
+
registerColumn(col) {
|
|
2437
|
+
this.columns.update(cols => [...cols, col]);
|
|
2438
|
+
}
|
|
2439
|
+
setCellValue(row, field, value) {
|
|
2440
|
+
const keys = field.split('.');
|
|
2441
|
+
const lastKey = keys.pop();
|
|
2442
|
+
const target = keys.reduce((acc, key) => {
|
|
2443
|
+
acc[key] ??= {};
|
|
2444
|
+
return acc[key];
|
|
2445
|
+
}, row);
|
|
2446
|
+
target[lastKey] = value;
|
|
2447
|
+
}
|
|
2448
|
+
onCellChange(row, col, value) {
|
|
2449
|
+
this.setCellValue(row, col.field(), value);
|
|
2450
|
+
this.cellValueEdited.emit({ [col.field()]: value });
|
|
2451
|
+
}
|
|
2452
|
+
onSearch(value) {
|
|
2453
|
+
this.searchTerm.set(value.toLowerCase());
|
|
2454
|
+
this.pageIndex.set(0);
|
|
2455
|
+
this.saveState();
|
|
2456
|
+
this.resetEditRow();
|
|
2457
|
+
}
|
|
2458
|
+
isActiveMatch(rowIndex, col) {
|
|
2459
|
+
const match = this.searchMatches()[this.activeMatchIndex()];
|
|
2460
|
+
return !!match && match.rowIndex === rowIndex && match.column === col;
|
|
2461
|
+
}
|
|
2462
|
+
nextMatch() {
|
|
2463
|
+
if (!this.searchMatches().length)
|
|
2464
|
+
return;
|
|
2465
|
+
this.activeMatchIndex.set((this.activeMatchIndex() + 1) % this.searchMatches().length);
|
|
2466
|
+
this.scrollToActiveMatch();
|
|
2467
|
+
}
|
|
2468
|
+
prevMatch() {
|
|
2469
|
+
if (!this.searchMatches().length)
|
|
2470
|
+
return;
|
|
2471
|
+
this.activeMatchIndex.set((this.activeMatchIndex() - 1 + this.searchMatches().length) %
|
|
2472
|
+
this.searchMatches().length);
|
|
2473
|
+
this.scrollToActiveMatch();
|
|
2474
|
+
}
|
|
2475
|
+
scrollToActiveMatch() {
|
|
2476
|
+
const match = this.searchMatches()[this.activeMatchIndex()];
|
|
2477
|
+
if (!match)
|
|
2478
|
+
return;
|
|
2479
|
+
const row = this.scrollContainer()?.nativeElement.querySelector(`tr[data-row-index="${match.rowIndex}"]`);
|
|
2480
|
+
row?.scrollIntoView({
|
|
2481
|
+
behavior: 'smooth',
|
|
2482
|
+
block: 'center',
|
|
2483
|
+
});
|
|
2484
|
+
}
|
|
2485
|
+
nextPage() {
|
|
2486
|
+
if (this.pageIndex() < this.totalPages() - 1) {
|
|
2487
|
+
this.pageIndex.update(val => val + 1);
|
|
2488
|
+
this.emitQueryIfNeeded();
|
|
2489
|
+
this.resetEditRow();
|
|
2490
|
+
}
|
|
2491
|
+
}
|
|
2492
|
+
prevPage() {
|
|
2493
|
+
if (this.pageIndex() > 0) {
|
|
2494
|
+
this.pageIndex.update(v => v - 1);
|
|
2495
|
+
this.emitQueryIfNeeded();
|
|
2496
|
+
this.resetEditRow();
|
|
2497
|
+
}
|
|
2498
|
+
}
|
|
2499
|
+
onPageSizeChange(size) {
|
|
2500
|
+
this.pageSize.set(Number(size));
|
|
2501
|
+
this.pageIndex.set(0);
|
|
2502
|
+
this.emitQueryIfNeeded();
|
|
2503
|
+
this.resetEditRow();
|
|
2504
|
+
}
|
|
2505
|
+
toggleVisibility(col) {
|
|
2506
|
+
if (!col.hideable())
|
|
2507
|
+
return;
|
|
2508
|
+
if (this.visibleCount() === 3 && col.visible())
|
|
2509
|
+
return;
|
|
2510
|
+
col.visible.update(val => !val);
|
|
2511
|
+
this.emitColumns();
|
|
2512
|
+
}
|
|
2513
|
+
drop(event) {
|
|
2514
|
+
if (event.previousIndex === event.currentIndex)
|
|
2515
|
+
return;
|
|
2516
|
+
this.columns.update(cols => {
|
|
2517
|
+
const copy = [...cols];
|
|
2518
|
+
moveItemInArray(copy, event.previousIndex, event.currentIndex);
|
|
2519
|
+
return copy;
|
|
2520
|
+
});
|
|
2521
|
+
this.emitColumns();
|
|
2522
|
+
}
|
|
2523
|
+
emitColumns() {
|
|
2524
|
+
this.saveState();
|
|
2525
|
+
this.columnsChanged.emit(this.columns().map(c => ({
|
|
2526
|
+
field: c.field(),
|
|
2527
|
+
visible: c.visible(),
|
|
2528
|
+
})));
|
|
2529
|
+
}
|
|
2530
|
+
restoreSort(sortField, sortDirection) {
|
|
2531
|
+
this.columns.update(cols => cols.map(col => {
|
|
2532
|
+
col.sortDirection.set(col.field() === sortField ? (sortDirection ?? null) : null);
|
|
2533
|
+
return col;
|
|
2534
|
+
}));
|
|
2535
|
+
}
|
|
2536
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.6", ngImport: i0, type: FuTableComponent, deps: [{ token: i0.Injector }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
2537
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.0.6", type: FuTableComponent, isStandalone: true, selector: "fu-table", inputs: { dataSource: { classPropertyName: "dataSource", publicName: "dataSource", isSignal: true, isRequired: true, transformFunction: null }, totalItems: { classPropertyName: "totalItems", publicName: "totalItems", isSignal: true, isRequired: false, transformFunction: null }, localStorageKey: { classPropertyName: "localStorageKey", publicName: "localStorageKey", isSignal: true, isRequired: false, transformFunction: null }, columnsConfig: { classPropertyName: "columnsConfig", publicName: "columnsConfig", isSignal: true, isRequired: false, transformFunction: null }, configPanel: { classPropertyName: "configPanel", publicName: "configPanel", isSignal: true, isRequired: false, transformFunction: null }, filterPanel: { classPropertyName: "filterPanel", publicName: "filterPanel", isSignal: true, isRequired: false, transformFunction: null }, paginator: { classPropertyName: "paginator", publicName: "paginator", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, disablePaginator: { classPropertyName: "disablePaginator", publicName: "disablePaginator", isSignal: true, isRequired: false, transformFunction: null }, resetQuery: { classPropertyName: "resetQuery", publicName: "resetQuery", isSignal: true, isRequired: false, transformFunction: null }, responsive: { classPropertyName: "responsive", publicName: "responsive", isSignal: true, isRequired: false, transformFunction: null }, scrollHeight: { classPropertyName: "scrollHeight", publicName: "scrollHeight", isSignal: true, isRequired: false, transformFunction: null }, scrollable: { classPropertyName: "scrollable", publicName: "scrollable", isSignal: true, isRequired: false, transformFunction: null }, stickyHeader: { classPropertyName: "stickyHeader", publicName: "stickyHeader", isSignal: true, isRequired: false, transformFunction: null }, stickyFirstColumn: { classPropertyName: "stickyFirstColumn", publicName: "stickyFirstColumn", isSignal: true, isRequired: false, transformFunction: null }, stickyLastColumn: { classPropertyName: "stickyLastColumn", publicName: "stickyLastColumn", isSignal: true, isRequired: false, transformFunction: null }, rowState: { classPropertyName: "rowState", publicName: "rowState", isSignal: true, isRequired: false, transformFunction: null }, rowClass: { classPropertyName: "rowClass", publicName: "rowClass", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { dataSource: "dataSourceChange", queryChanged: "queryChanged", cellValueEdited: "cellValueEdited", columnsChanged: "columnsChanged", rowEditSubmit: "rowEditSubmit", rowClicked: "rowClicked" }, queries: [{ propertyName: "sidebarTemplate", first: true, predicate: FuTableSidebarTemplateDirective, descendants: true, read: TemplateRef, isSignal: true }, { propertyName: "noDataTemplate", first: true, predicate: FuTableNoDataTemplateDirective, descendants: true, read: TemplateRef, isSignal: true }, { propertyName: "expandRowTemplate", first: true, predicate: FuTableExpandRowTemplateDirective, descendants: true, read: TemplateRef, isSignal: true }, { propertyName: "rowContextMenuTemplate", first: true, predicate: FuRowContextMenuTemplateDirective, descendants: true, read: TemplateRef, isSignal: true }], viewQueries: [{ propertyName: "scrollContainer", first: true, predicate: ["scrollContainer"], descendants: true, isSignal: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"fu-table-wrapper\">\r\n @if (filterPanel()) {\r\n <div class=\"fu-search\">\r\n <input\r\n type=\"search\"\r\n [placeholder]=\"intl.searchPlaceholder()\"\r\n [value]=\"searchTerm()\"\r\n (input)=\"onSearch($any($event.target).value)\"\r\n />\r\n <div class=\"fu-search-nav\">\r\n <button fuButton (click)=\"prevMatch()\">\r\n {{ intl.previous() }}\r\n </button>\r\n <button fuButton (click)=\"nextMatch()\">\r\n {{ intl.next() }}\r\n </button>\r\n @if (searchMatches().length > 0) {\r\n <span class=\"fu-match-counter\">\r\n {{ activeMatchIndex() + 1 }} / {{ searchMatches().length }}\r\n </span>\r\n }\r\n </div>\r\n </div>\r\n }\r\n\r\n <div\r\n class=\"fu-table-layout\"\r\n [class.fu-with-sidebar]=\"configPanel() || sidebarTemplate()\"\r\n >\r\n <div\r\n #scrollContainer\r\n class=\"fu-table-scroll\"\r\n [class.fu-scrollable]=\"scrollableConfig()\"\r\n [style.max-height.vh]=\"scrollHeightConfig()\"\r\n >\r\n <table\r\n class=\"fu-table\"\r\n [class.fu-table--responsive]=\"responsiveConfig()\"\r\n [class.fu-table-scrollable]=\"scrollableConfig()\"\r\n [class.fu-sticky-header]=\"stickyHeaderConfig()\"\r\n [class.fu-sticky-first]=\"stickyFirstColumnConfig()\"\r\n [class.fu-sticky-last]=\"stickyLastColumnConfig()\"\r\n >\r\n <thead>\r\n <tr>\r\n @if (expandRowTemplate()) {\r\n <th class=\"fu-expand-header\"></th>\r\n }\r\n <!-- -->\r\n @for (col of visibleColumns(); track $index) {\r\n <th\r\n (click)=\"onSort(col, $event)\"\r\n [class.fu-sortable]=\"col.sortable()\"\r\n [style.width]=\"col.width()\"\r\n >\r\n <div class=\"fu-th-content\">\r\n @if (col.columnHeaderTemplate()) {\r\n <ng-container\r\n [ngTemplateOutlet]=\"col.columnHeaderTemplate()!\"\r\n [ngTemplateOutletContext]=\"{ index: $index }\"\r\n />\r\n @if (col.sortable()) {\r\n <ng-container\r\n [ngTemplateOutlet]=\"defaultSortIcon\"\r\n [ngTemplateOutletContext]=\"{\r\n $implicit: col.sortDirection(),\r\n }\"\r\n />\r\n }\r\n } @else {\r\n <span class=\"fu-header-main\">\r\n {{ col.header() }}\r\n\r\n @if (col.sortable()) {\r\n <ng-container\r\n [ngTemplateOutlet]=\"defaultSortIcon\"\r\n [ngTemplateOutletContext]=\"{\r\n $implicit: col.sortDirection(),\r\n }\"\r\n />\r\n }\r\n </span>\r\n }\r\n <!-- -->\r\n @if (col.columnHeaderAddonTemplate()) {\r\n <span class=\"fu-header-addon\">\r\n <ng-container\r\n [ngTemplateOutlet]=\"col.columnHeaderAddonTemplate()!\"\r\n [ngTemplateOutletContext]=\"{ index: $index }\"\r\n />\r\n </span>\r\n }\r\n </div>\r\n </th>\r\n }\r\n </tr>\r\n </thead>\r\n\r\n <tbody>\r\n @if (loading() && pendingSaveRowIndex() === null) {\r\n <ng-container [ngTemplateOutlet]=\"loadingTemplate\" />\r\n } @else {\r\n <!-- -->\r\n @for (row of visibleRows(); let rowIndex = $index; track rowIndex) {\r\n <tr\r\n #contextTrigger=\"cdkContextMenuTriggerFor\"\r\n [cdkContextMenuTriggerFor]=\"rowContextMenu\"\r\n [cdkContextMenuTriggerData]=\"{\r\n row,\r\n index: rowIndex,\r\n trigger: contextTrigger,\r\n }\"\r\n [cdkContextMenuDisabled]=\"!rowContextMenuTemplate()\"\r\n [class.fu-cell-success]=\"\r\n rowStates.get(rowIndex) === 'success' && !loading()\r\n \"\r\n [class.fu-cell-error]=\"\r\n rowStates.get(rowIndex) === 'error' && !loading()\r\n \"\r\n [class.fu-row-expanded]=\"expandIndex() === rowIndex\"\r\n [attr.data-row-index]=\"rowIndex\"\r\n [class]=\"rowClass() ? rowClass()!(row, rowIndex) : ''\"\r\n (animationend)=\"onEditAnimationEnd()\"\r\n (click)=\"onRowClick(row, $event)\"\r\n >\r\n @if (expandRowTemplate()) {\r\n <td\r\n class=\"fu-expand-cell\"\r\n [attr.data-expand]=\"expandIndex() === rowIndex\"\r\n (click)=\"toggleExpand(rowIndex, $event)\"\r\n >\r\n <span class=\"fu-icon\">\r\n <svg\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n width=\"24\"\r\n height=\"24\"\r\n viewBox=\"0 0 24 24\"\r\n >\r\n <path\r\n fill=\"currentColor\"\r\n d=\"M10 6L8.59 7.41L13.17 12l-4.58 4.59L10 18l6-6z\"\r\n />\r\n </svg>\r\n </span>\r\n </td>\r\n }\r\n <!-- -->\r\n @for (\r\n col of visibleColumns();\r\n let colIndex = $index;\r\n track colIndex\r\n ) {\r\n @let isMatch = isActiveMatch(rowIndex, col);\r\n\r\n <td\r\n [class.fu-cell-pending]=\"\r\n rowStates.get(rowIndex) === 'pending' && loading()\r\n \"\r\n [class.fu-cell-editing]=\"editingRowIndex() === rowIndex\"\r\n [class.fu-cell-sorting]=\"\r\n col.sortable() && col.sortDirection() !== null\r\n \"\r\n [class.fu-active-match]=\"isMatch\"\r\n >\r\n <div class=\"fu-header-text\">\r\n {{ col.header() }}\r\n </div>\r\n\r\n @if (col.columnCellTemplate()) {\r\n <ng-container\r\n [ngTemplateOutlet]=\"col.columnCellTemplate()!\"\r\n [ngTemplateOutletContext]=\"{\r\n $implicit: getCellValue(row, col.field()),\r\n row: row,\r\n index: rowIndex,\r\n searchTerm: searchTerm(),\r\n activeMatch: isMatch,\r\n }\"\r\n }\r\n />\r\n } @else if (col.columnActionsTemplate()) {\r\n <ng-container\r\n [ngTemplateOutlet]=\"col.columnActionsTemplate()!\"\r\n [ngTemplateOutletContext]=\"{\r\n row: row,\r\n index: rowIndex,\r\n editing: editingRowIndex() === rowIndex,\r\n startEdit: startEditRow.bind(this),\r\n saveEdit: saveEditRow.bind(this),\r\n cancelEdit: cancelEditRow.bind(this),\r\n toggleExpand: toggleExpand.bind(this),\r\n }\"\r\n }\r\n />\r\n } @else {\r\n <fu-cell\r\n [type]=\"col.type()\"\r\n [searchTerm]=\"searchTerm()\"\r\n [editable]=\"col.editable()\"\r\n [value]=\"getCellValue(row, col.field())\"\r\n [activeMatch]=\"isMatch\"\r\n [editing]=\"editingRowIndex() === rowIndex\"\r\n (cellValueChange)=\"onCellChange(row, col, $event)\"\r\n />\r\n }\r\n </td>\r\n }\r\n </tr>\r\n\r\n @if (expandIndex() === rowIndex) {\r\n <tr>\r\n <td [attr.colspan]=\"columns().length + 1\">\r\n <ng-container\r\n [ngTemplateOutlet]=\"expandRowTemplate()!\"\r\n [ngTemplateOutletContext]=\"{\r\n $implicit: row,\r\n index: rowIndex,\r\n searchTerm: searchTerm,\r\n }\"\r\n />\r\n </td>\r\n </tr>\r\n }\r\n } @empty {\r\n <tr class=\"fu-no-data-row\">\r\n <td [attr.colspan]=\"columns().length + 1\">\r\n <ng-container\r\n [ngTemplateOutlet]=\"noDataTemplate() ?? defaultNoData\"\r\n />\r\n </td>\r\n </tr>\r\n }\r\n }\r\n </tbody>\r\n </table>\r\n </div>\r\n\r\n @if (configPanel() || sidebarTemplate()) {\r\n <div\r\n class=\"fu-sidebar\"\r\n [class.fu-open]=\"isSidebarOpen()\"\r\n (fuClickOutside)=\"activeSidebarPanel.set(null)\"\r\n >\r\n @if (isSidebarOpen()) {\r\n <div class=\"fu-content\">\r\n @if (isColumnsOpen()) {\r\n <ng-container [ngTemplateOutlet]=\"defaultSidebar\" />\r\n } @else if (isLayoutOpen()) {\r\n <ng-container [ngTemplateOutlet]=\"configSidebar\" />\r\n } @else if (isCustomOpen()) {\r\n <ng-container [ngTemplateOutlet]=\"sidebarTemplate()!\" />\r\n }\r\n </div>\r\n }\r\n\r\n <div class=\"fu-handle\">\r\n @if (configPanel()) {\r\n <button\r\n fuButton\r\n variant=\"icon\"\r\n severity=\"success\"\r\n (click)=\"toggleColumnsPanel()\"\r\n >\r\n <svg\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n width=\"24\"\r\n height=\"24\"\r\n viewBox=\"0 0 24 24\"\r\n >\r\n <path\r\n fill=\"currentColor\"\r\n d=\"M3.25 3a.75.75 0 0 0 0 1.5h.5C4.44 4.5 5 5.06 5 5.75v12.5c0 .69-.56 1.25-1.25 1.25h-.5a.75.75 0 0 0 0 1.5h.5a2.75 2.75 0 0 0 2.75-2.75V5.75A2.75 2.75 0 0 0 3.75 3zm7.5 0A2.75 2.75 0 0 0 8 5.75v12.5A2.75 2.75 0 0 0 10.75 21h.415l.356-1.423l.02-.077h-.791c-.69 0-1.25-.56-1.25-1.25V5.75c0-.69.56-1.25 1.25-1.25h2.5c.69 0 1.25.56 1.25 1.25v10.105l1.5-1.5V5.75A2.75 2.75 0 0 0 13.25 3zM19 11.483q-.325.198-.607.48l-.893.892V5.75A2.75 2.75 0 0 1 20.25 3h.5a.75.75 0 0 1 0 1.5h-.5c-.69 0-1.25.56-1.25 1.25zm.1 1.186l-5.903 5.903a2.7 2.7 0 0 0-.706 1.247l-.458 1.831a1.087 1.087 0 0 0 1.319 1.318l1.83-.457a2.7 2.7 0 0 0 1.248-.707l5.902-5.902A2.286 2.286 0 0 0 19.1 12.67\"\r\n />\r\n </svg>\r\n </button>\r\n <button\r\n fuButton\r\n variant=\"icon\"\r\n severity=\"warning\"\r\n (click)=\"toggleLayoutPanel()\"\r\n >\r\n <svg\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n width=\"24\"\r\n height=\"24\"\r\n viewBox=\"0 0 24 24\"\r\n >\r\n <path\r\n fill=\"currentColor\"\r\n d=\"M21 13.1c-.1 0-.3.1-.4.2l-1 1l2.1 2.1l1-1c.2-.2.2-.6 0-.8l-1.3-1.3c-.1-.1-.2-.2-.4-.2m-1.9 1.8l-6.1 6V23h2.1l6.1-6.1zM21 3h-8v6h8zm-2 4h-4V5h4zm-6 11.06V11h8v.1c-.76 0-1.43.4-1.81.79L18.07 13H15v3.07zM11 3H3v10h8zm-2 8H5V5h4zm2 9.06V15H3v6h8zM9 19H5v-2h4z\"\r\n />\r\n </svg>\r\n </button>\r\n }\r\n @if (sidebarTemplate()) {\r\n <button\r\n fuButton\r\n variant=\"icon\"\r\n severity=\"danger\"\r\n (click)=\"toggleCustomPanel()\"\r\n >\r\n <svg\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n width=\"24\"\r\n height=\"24\"\r\n viewBox=\"0 0 24 24\"\r\n >\r\n <path\r\n fill=\"currentColor\"\r\n d=\"M12 16c1.1 0 2 .9 2 2s-.9 2-2 2s-2-.9-2-2s.9-2 2-2m0-6c1.1 0 2 .9 2 2s-.9 2-2 2s-2-.9-2-2s.9-2 2-2m0-6c1.1 0 2 .9 2 2s-.9 2-2 2s-2-.9-2-2s.9-2 2-2M6 16c1.1 0 2 .9 2 2s-.9 2-2 2s-2-.9-2-2s.9-2 2-2m0-6c1.1 0 2 .9 2 2s-.9 2-2 2s-2-.9-2-2s.9-2 2-2m0-6c1.1 0 2 .9 2 2s-.9 2-2 2s-2-.9-2-2s.9-2 2-2m12 12c1.1 0 2 .9 2 2s-.9 2-2 2s-2-.9-2-2s.9-2 2-2m0-6c1.1 0 2 .9 2 2s-.9 2-2 2s-2-.9-2-2s.9-2 2-2m0-6c1.1 0 2 .9 2 2s-.9 2-2 2s-2-.9-2-2s.9-2 2-2\"\r\n />\r\n </svg>\r\n </button>\r\n }\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n\r\n @if (paginator()) {\r\n <div class=\"fu-paginator\">\r\n <div class=\"fu-paginator-entries\">\r\n {{ entriesLabel() }}\r\n </div>\r\n\r\n <button\r\n fuButton\r\n variant=\"icon\"\r\n (click)=\"prevPage()\"\r\n [disabled]=\"pageIndex() === 0 || disablePaginator()\"\r\n >\r\n <svg\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n width=\"24\"\r\n height=\"24\"\r\n viewBox=\"0 0 24 24\"\r\n >\r\n <path\r\n fill=\"none\"\r\n stroke=\"currentColor\"\r\n stroke-linecap=\"round\"\r\n stroke-linejoin=\"round\"\r\n stroke-width=\"1.5\"\r\n d=\"m14 7l-5 5l5 5\"\r\n />\r\n </svg>\r\n </button>\r\n\r\n <span class=\"fu-page-info\">\r\n {{ intl.pageLabel() }} {{ pageIndex() + 1 }} {{ intl.ofLabel() }}\r\n {{ totalPages() }}\r\n </span>\r\n\r\n <button\r\n fuButton\r\n variant=\"icon\"\r\n (click)=\"nextPage()\"\r\n [disabled]=\"pageIndex() >= totalPages() - 1 || disablePaginator()\"\r\n >\r\n <svg\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n width=\"24\"\r\n height=\"24\"\r\n viewBox=\"0 0 24 24\"\r\n >\r\n <path\r\n fill=\"none\"\r\n stroke=\"currentColor\"\r\n stroke-linecap=\"round\"\r\n stroke-linejoin=\"round\"\r\n stroke-width=\"1.5\"\r\n d=\"m10 17l5-5l-5-5\"\r\n />\r\n </svg>\r\n </button>\r\n\r\n <select [(ngModel)]=\"pageSize\" (ngModelChange)=\"onPageSizeChange($event)\">\r\n @for (size of pageSizeOptions(); track size) {\r\n <option class=\"fu-option\" [value]=\"size\">{{ size }}</option>\r\n }\r\n </select>\r\n </div>\r\n }\r\n</div>\r\n\r\n<ng-template\r\n #rowContextMenu\r\n let-row=\"row\"\r\n let-index=\"index\"\r\n let-trigger=\"trigger\"\r\n>\r\n <div cdkMenu>\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n rowContextMenuTemplate()!;\r\n context: { row, index, trigger }\r\n \"\r\n />\r\n </div>\r\n</ng-template>\r\n\r\n<ng-template #loadingTemplate>\r\n @for (\r\n i of [].constructor(skeletonRowCount());\r\n let rowIndex = $index;\r\n track rowIndex\r\n ) {\r\n <tr class=\"fu-skeleton-row\">\r\n @for (\r\n j of [].constructor(skeletonColumnCount());\r\n let colIndex = $index;\r\n track colIndex\r\n ) {\r\n <td>\r\n <div class=\"fu-skeleton-cell\"></div>\r\n </td>\r\n }\r\n </tr>\r\n }\r\n</ng-template>\r\n\r\n<ng-template #defaultSortIcon let-direction>\r\n <span class=\"fu-sort-icon\">\r\n @if (!direction) {\r\n <svg\r\n class=\"fu-sort-icon-default\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n width=\"16\"\r\n height=\"16\"\r\n viewBox=\"0 0 24 24\"\r\n >\r\n <path\r\n fill=\"none\"\r\n stroke=\"currentColor\"\r\n stroke-linecap=\"round\"\r\n stroke-linejoin=\"round\"\r\n stroke-width=\"2\"\r\n d=\"m3 9l4-4l4 4M7 5v14m14-4l-4 4l-4-4m4 4V5\"\r\n />\r\n </svg>\r\n }\r\n\r\n @if (direction === 'asc') {\r\n <svg\r\n class=\"fu-sort-icon-active\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n width=\"16\"\r\n height=\"16\"\r\n viewBox=\"0 0 24 24\"\r\n >\r\n <path\r\n fill=\"none\"\r\n stroke=\"currentColor\"\r\n stroke-linecap=\"round\"\r\n stroke-linejoin=\"round\"\r\n stroke-width=\"2\"\r\n d=\"M4 6h7m-7 6h7m-7 6h9m2-9l3-3l3 3m-3-3v12\"\r\n />\r\n </svg>\r\n }\r\n\r\n @if (direction === 'desc') {\r\n <svg\r\n class=\"fu-sort-icon-active\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n width=\"16\"\r\n height=\"16\"\r\n viewBox=\"0 0 24 24\"\r\n >\r\n <path\r\n fill=\"none\"\r\n stroke=\"currentColor\"\r\n stroke-linecap=\"round\"\r\n stroke-linejoin=\"round\"\r\n stroke-width=\"2\"\r\n d=\"M4 6h9m-9 6h7m-7 6h7m4-3l3 3l3-3m-3-9v12\"\r\n />\r\n </svg>\r\n }\r\n </span>\r\n</ng-template>\r\n\r\n<ng-template #defaultNoData>\r\n <div class=\"fu-no-data-default\">{{ intl.noData() }}</div>\r\n</ng-template>\r\n\r\n<ng-template #configSidebar>\r\n <div class=\"fu-config-sidebar\">\r\n <div class=\"fu-config-item\">\r\n <label for=\"responsive\">{{ intl.responsiveLabel() }}</label>\r\n <input id=\"responsive\" type=\"checkbox\" [(ngModel)]=\"responsiveConfig\" />\r\n </div>\r\n\r\n <div class=\"fu-config-item\">\r\n <label for=\"scrollable\">{{ intl.scrollableLabel() }}</label>\r\n <input id=\"scrollable\" type=\"checkbox\" [(ngModel)]=\"scrollableConfig\" />\r\n </div>\r\n\r\n <div class=\"fu-config-item\">\r\n <label for=\"stickyHeader\">{{ intl.lockHeaderLabel() }}</label>\r\n <input\r\n id=\"stickyHeader\"\r\n type=\"checkbox\"\r\n [(ngModel)]=\"stickyHeaderConfig\"\r\n [disabled]=\"!scrollableConfig()\"\r\n />\r\n </div>\r\n\r\n <div class=\"fu-config-item\">\r\n <label for=\"stickyFirstColumn\">{{ intl.lockLeftLabel() }}</label>\r\n <input\r\n id=\"stickyFirstColumn\"\r\n type=\"checkbox\"\r\n [(ngModel)]=\"stickyFirstColumnConfig\"\r\n [disabled]=\"!scrollableConfig()\"\r\n />\r\n </div>\r\n\r\n <div class=\"fu-config-item\">\r\n <label for=\"stickyLastColumn\">{{ intl.lockRightLabel() }}</label>\r\n <input\r\n id=\"stickyLastColumn\"\r\n type=\"checkbox\"\r\n [(ngModel)]=\"stickyLastColumnConfig\"\r\n [disabled]=\"!scrollableConfig()\"\r\n />\r\n </div>\r\n </div>\r\n</ng-template>\r\n\r\n<ng-template #defaultSidebar>\r\n <div\r\n class=\"fu-columns-panel\"\r\n cdkDropList\r\n cdkDropListOrientation=\"vertical\"\r\n (cdkDropListDropped)=\"drop($event)\"\r\n >\r\n <div class=\"fu-drag-lock\">\r\n @for (col of columns(); track col.field()) {\r\n <div\r\n class=\"fu-column-item\"\r\n cdkDrag\r\n cdkDragLockAxis=\"y\"\r\n cdkDragBoundary=\".fu-drag-lock\"\r\n [cdkDragDisabled]=\"!col.reorderable()\"\r\n >\r\n <span class=\"fu-drag-handle\" cdkDragHandle>\u2630</span>\r\n\r\n <span class=\"fu-label\">{{ col.header() }}</span>\r\n\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"col.visible()\"\r\n [disabled]=\"\r\n !col.hideable() || (visibleCount() === 3 && col.visible())\r\n \"\r\n (change)=\"toggleVisibility(col)\"\r\n />\r\n\r\n <ng-template cdkDragPreview>\r\n <div class=\"fu-drag-preview\">\r\n <svg\r\n class=\"fu-drag-icon\"\r\n width=\"16px\"\r\n height=\"16px\"\r\n fill=\"currentColor\"\r\n viewBox=\"0 0 25 25\"\r\n >\r\n <path\r\n d=\"M10 9h4V6h3l-5-5-5 5h3v3zm-1 1H6V7l-5 5 5 5v-3h3v-4zm14 2l-5-5v3h-3v4h3v3l5-5zm-9 3h-4v3H7l5 5 5-5h-3v-3z\"\r\n ></path>\r\n </svg>\r\n <div class=\"fu-drag-label\">{{ col.header() }}</div>\r\n </div>\r\n </ng-template>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n</ng-template>\r\n", styles: [".fu-table-wrapper{--fu-table-bg: oklch(from var(--fu-surface) l c h);--fu-table-text: oklch(from var(--fu-text) l c h);--fu-table-border: oklch(from var(--fu-border) l c h);--fu-table-input-bg: oklch(from var(--fu-surface) calc(l - .03) c h);--fu-table-header-bg: oklch(from var(--fu-surface) calc(l - .04) c h);--fu-table-header-text: oklch(from var(--fu-text) l c h);--fu-table-header-border: oklch(from var(--fu-border) calc(l - .02) c h);--fu-table-row-bg: oklch(from var(--fu-surface) l c h);--fu-table-row-hover-bg: oklch(from var(--fu-surface) calc(l - .035) c h);--fu-table-row-border: oklch(from var(--fu-border) l c h);--fu-table-row-striped-bg: oklch(from var(--fu-surface) calc(l - .02) c h);--fu-table-row-active-bg: oklch( from var(--fu-accent) calc(l + .3) calc(c/5) h );--fu-table-row-pending-bg: oklch( from var(--fu-warning) calc(l + .25) calc(c/4) h );--fu-table-sticky-shadow: color-mix( in oklch, oklch(from var(--fu-text) .2 0 0) 30%, transparent );--fu-table-sticky-bg: oklch(from var(--fu-surface) calc(l - .05) c h);--fu-table-sticky-border: oklch(from var(--fu-border) calc(l - .08) c h);--fu-skeleton-base: color-mix( in srgb, var(--fu-table-row-bg) 90%, var(--fu-text) 10% );--fu-skeleton-highlight: color-mix( in srgb, var(--fu-table-row-bg) 70%, var(--fu-text) 30% );margin:16px;border:2px solid var(--fu-table-border);font-family:var(--fu-font-family);font-size:var(--fu-font-size)}.fu-table-scroll.fu-scrollable{width:100%;overflow-x:auto;overflow-y:auto}.fu-table-scroll,.fu-table{width:100%}.fu-scrollable{overflow-x:auto;overflow-y:auto}.fu-table-scrollable{width:max-content;min-width:100%;table-layout:auto}.fu-sticky-header thead th{position:sticky;top:0;z-index:20;background:var(--fu-table-sticky-bg);border-bottom:1px solid var(--fu-table-sticky-border)}.fu-sticky-first th:first-child,.fu-sticky-first td:first-child{position:sticky;left:0;z-index:10;background:var(--fu-table-sticky-bg);border-right:1px solid var(--fu-table-sticky-border)}.fu-sticky-header.fu-sticky-first thead th:first-child{z-index:30}.fu-sticky-last th:last-child,.fu-sticky-last td:last-child{position:sticky;right:0;z-index:10;background:var(--fu-table-sticky-bg);border-left:1px solid var(--fu-table-sticky-border)}.fu-sticky-header.fu-sticky-last thead th:last-child{z-index:30}.fu-sticky-first td:first-child{box-shadow:4px 0 6px -2px var(--fu-table-sticky-shadow)}.fu-sticky-last td:last-child{box-shadow:-4px 0 6px -2px var(--fu-table-sticky-shadow)}.fu-sticky-header thead th{box-shadow:0 4px 6px -2px var(--fu-table-sticky-shadow)}table{border-collapse:separate;border-spacing:0}tr{height:42px}th,td{padding:0 8px;background-color:var(--fu-table-bg);color:var(--fu-table-text);border-right:1px solid var(--fu-table-border);border-bottom:1px solid var(--fu-table-border);vertical-align:middle}th{text-align:left;vertical-align:middle}td .fu-icon{display:flex;justify-content:center;align-items:center;transition:transform .15s cubic-bezier(.4,0,.2,1);transform-origin:center}td[data-expand=true] .fu-icon{transform:rotate(90deg)}thead th{background-color:var(--fu-table-header-bg);border-top:2px solid var(--fu-table-border)}tr:nth-child(2n) td{background-color:var(--fu-table-row-striped-bg)}tbody tr:hover td{background-color:var(--fu-table-row-hover-bg)}th.fu-sortable{cursor:pointer;-webkit-user-select:none;user-select:none}th.fu-sortable:hover{background-color:var(--fu-table-row-hover-bg)}tbody td.fu-cell-sorting{font-weight:600}.fu-th-content{display:flex;align-items:center;gap:8px}.fu-header-main{flex:1;display:inline-flex;align-items:center;gap:4px}.fu-header-addon{display:inline-flex;align-items:center;border-color:var(--fu-table-sticky-border)}.fu-paginator{display:flex;align-items:center;justify-content:flex-end;gap:12px;padding:8px 16px;font-size:14px;background-color:var(--fu-table-bg);color:var(--fu-table-text)}.fu-page-info{white-space:nowrap;color:var(--fu-table-text)}.fu-paginator select{height:32px;padding:4px 8px;border:1px solid var(--fu-table-border);border-radius:0;background-color:var(--fu-table-row-striped-bg);cursor:pointer;color:var(--fu-table-text)}.fu-paginator select:focus{border-color:var(--fu-table-sticky-border);outline:none}.fu-search{display:flex;align-items:center;padding:8px 16px;background-color:var(--fu-table-bg);gap:16px}.fu-search input[type=search]{width:25%;max-width:100%;padding:6px 10px;font-size:14px;line-height:1.4;border:1px solid var(--fu-table-border);border-radius:var(--fu-border-radius);background-color:var(--fu-table-row-striped-bg);color:var(--fu-table-text);outline:none;transition:border-color .15s,box-shadow .15s}.fu-search input[type=search]::placeholder{color:#999}.fu-search input[type=search]:focus{border-color:var(--fu-table-sticky-border)}tbody tr.fu-no-data-row td{text-align:center;padding:24px;background-color:var(--fu-table-bg);color:var(--fu-table-text);border-right-color:var(--fu-table-border);height:207px;pointer-events:none}.fu-no-data-default{font-style:italic}.fu-skeleton-row td{padding:10px 8px}.fu-skeleton-cell{height:20px;width:100%;border-radius:4px;background-image:linear-gradient(90deg,var(--fu-skeleton-base) 25%,var(--fu-skeleton-highlight) 37%,var(--fu-skeleton-base) 63%);background-size:400% 100%;animation:skeleton-loading 1.4s ease infinite}@media (prefers-reduced-motion: reduce){.fu-skeleton-cell{animation:none;opacity:.6}}@keyframes skeleton-loading{0%{background-position:100% 50%}to{background-position:0 50%}}tbody tr.fu-cell-error td{animation:flash-error 1.6s ease-out}@keyframes flash-error{0%{background-color:#fecaca}to{background-color:var(--fu-table-bg)}}tbody tr.fu-cell-success td{animation:flash-success 1.6s ease-out}@keyframes flash-success{0%{background-color:#98efa9}to{background-color:var(--fu-table-bg)}}tbody tr td.fu-cell-pending{pointer-events:none;animation:flash-pending 1.4s ease-in-out infinite}@keyframes flash-pending{0%,to{background-color:var(--fu-table-row-bg)}50%{background-color:var(--fu-table-row-pending-bg)}}.fu-table-layout{position:relative}.fu-table-layout.fu-with-sidebar{padding-right:43px}.fu-sidebar{position:absolute;right:0;bottom:0;top:0;width:41.5px;transition:width .25s ease;background-color:var(--fu-table-bg);border:2px solid var(--fu-table-border);border-right:none;overflow:hidden;display:grid;z-index:40}.fu-sidebar.fu-open{min-width:230px}.fu-handle{position:absolute;right:0;background-color:var(--fu-table-bg);height:100%;display:flex;flex-direction:column;align-items:center;gap:8px;padding:4px}.fu-content{border-right:2px solid var(--fu-table-border);overflow-y:auto;width:189px}.fu-columns-panel{display:flex;flex-direction:column;margin:4px 4px 0}.fu-column-item{display:flex;align-items:center;gap:8px;padding:6px 8px;margin-bottom:4px;background:var(--fu-table-header-bg);border:1px solid var(--fu-table-border);border-radius:var(--fu-border-radius)}.fu-column-item:hover{background:var(--fu-table-row-hover-bg)}.fu-column-item .fu-drag-handle{cursor:grab}.fu-config-item{background-color:var(--fu-table-header-bg);border:1px solid var(--fu-table-border)}.fu-config-item label{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fu-header-text{display:none}@media (max-width: 900px){.fu-table--responsive thead{display:none}.fu-table--responsive tbody{display:block}.fu-table--responsive tr{display:block;height:auto;margin-bottom:4px;border-top:2px solid var(--fu-table-border)}.fu-table--responsive tr.fu-row-expanded{margin-bottom:0}.fu-table--responsive th{display:block}.fu-table--responsive td{display:flex;align-items:center;justify-content:space-between;height:42px;border-bottom:1px solid var(--fu-table-border)}.fu-table--responsive .fu-header-text{display:block}}.fu-outline-1{outline:1px solid var(--fu-table-border)}.fu-expand-cell{color:var(--fu-accent);cursor:pointer}.fu-search-nav{display:flex;gap:8px}.fu-match-counter{display:flex;align-items:center;margin-left:8px}.fu-expand-header{width:40px}.fu-header-addon{border-left:1px solid var(--fu-table-sticky-border);padding-left:8px;margin-left:8px}.fu-paginator-entries{margin-right:auto}.fu-sort-icon{margin-top:4px}.fu-sort-icon-default{color:#c4cbd5}.fu-config-sidebar{display:flex;flex-direction:column;gap:4px;padding:4px}.fu-config-item{display:flex;gap:8px;justify-content:space-between;padding:8px;background-color:var(--fu-table-header-bg)}.fu-config-item:hover{background-color:var(--fu-table-row-hover-bg)}.fu-label{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fu-drag-preview{display:flex;align-items:center;background-color:var(--fu-table-header-bg);border:1px solid var(--fu-table-border);border-radius:var(--fu-border-radius);box-shadow:0 4px 6px -2px var(--fu-table-sticky-shadow)}.fu-drag-icon{margin:0 8px}.fu-drag-label{padding:8px 12px}.fu-context-menu{background:var(--fu-table-bg);border:1px solid var(--fu-table-border);box-shadow:0 2px 5px #00000059;display:inline-flex;flex-direction:column;min-width:180px;max-width:280px;padding:6px 0}::-webkit-scrollbar{width:8px;height:8px}::-webkit-scrollbar-track{background:var(--fu-table-bg);border-radius:10px}::-webkit-scrollbar-thumb{background:var(--fu-table-border);border-radius:10px}::-webkit-scrollbar-thumb:hover{background:#9ca3af}*{scrollbar-width:thin;scrollbar-color:var(--fu-table-border) var(--fu-table-bg)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: FuCellComponent, selector: "fu-cell", inputs: ["value", "type", "editing", "editable", "searchTerm", "activeMatch", "loading"], outputs: ["valueChange", "cellValueChange"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i2.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i3.CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: i3.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.CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }, { kind: "directive", type: i3.CdkDragPreview, selector: "ng-template[cdkDragPreview]", inputs: ["data", "matchSize"] }, { kind: "directive", type: FuClickOutsideDirective, selector: "[fuClickOutside]", outputs: ["fuClickOutside"] }, { kind: "directive", type: CdkContextMenuTrigger, selector: "[cdkContextMenuTriggerFor]", inputs: ["cdkContextMenuTriggerFor", "cdkContextMenuPosition", "cdkContextMenuTriggerData", "cdkContextMenuDisabled"], outputs: ["cdkContextMenuOpened", "cdkContextMenuClosed"], exportAs: ["cdkContextMenuTriggerFor"] }, { kind: "directive", type: FuButtonDirective, selector: "button[fuButton], a[fuButton]", inputs: ["variant", "severity", "loading", "disabled"] }, { kind: "directive", type: CdkMenu, selector: "[cdkMenu]", outputs: ["closed"], exportAs: ["cdkMenu"] }] }); }
|
|
2538
|
+
}
|
|
2539
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.6", ngImport: i0, type: FuTableComponent, decorators: [{
|
|
2540
|
+
type: Component,
|
|
2541
|
+
args: [{ selector: 'fu-table', imports: [
|
|
2542
|
+
CommonModule,
|
|
2543
|
+
FuCellComponent,
|
|
2544
|
+
FormsModule,
|
|
2545
|
+
DragDropModule,
|
|
2546
|
+
FuClickOutsideDirective,
|
|
2547
|
+
CdkContextMenuTrigger,
|
|
2548
|
+
FuButtonDirective,
|
|
2549
|
+
CdkMenu,
|
|
2550
|
+
], template: "<div class=\"fu-table-wrapper\">\r\n @if (filterPanel()) {\r\n <div class=\"fu-search\">\r\n <input\r\n type=\"search\"\r\n [placeholder]=\"intl.searchPlaceholder()\"\r\n [value]=\"searchTerm()\"\r\n (input)=\"onSearch($any($event.target).value)\"\r\n />\r\n <div class=\"fu-search-nav\">\r\n <button fuButton (click)=\"prevMatch()\">\r\n {{ intl.previous() }}\r\n </button>\r\n <button fuButton (click)=\"nextMatch()\">\r\n {{ intl.next() }}\r\n </button>\r\n @if (searchMatches().length > 0) {\r\n <span class=\"fu-match-counter\">\r\n {{ activeMatchIndex() + 1 }} / {{ searchMatches().length }}\r\n </span>\r\n }\r\n </div>\r\n </div>\r\n }\r\n\r\n <div\r\n class=\"fu-table-layout\"\r\n [class.fu-with-sidebar]=\"configPanel() || sidebarTemplate()\"\r\n >\r\n <div\r\n #scrollContainer\r\n class=\"fu-table-scroll\"\r\n [class.fu-scrollable]=\"scrollableConfig()\"\r\n [style.max-height.vh]=\"scrollHeightConfig()\"\r\n >\r\n <table\r\n class=\"fu-table\"\r\n [class.fu-table--responsive]=\"responsiveConfig()\"\r\n [class.fu-table-scrollable]=\"scrollableConfig()\"\r\n [class.fu-sticky-header]=\"stickyHeaderConfig()\"\r\n [class.fu-sticky-first]=\"stickyFirstColumnConfig()\"\r\n [class.fu-sticky-last]=\"stickyLastColumnConfig()\"\r\n >\r\n <thead>\r\n <tr>\r\n @if (expandRowTemplate()) {\r\n <th class=\"fu-expand-header\"></th>\r\n }\r\n <!-- -->\r\n @for (col of visibleColumns(); track $index) {\r\n <th\r\n (click)=\"onSort(col, $event)\"\r\n [class.fu-sortable]=\"col.sortable()\"\r\n [style.width]=\"col.width()\"\r\n >\r\n <div class=\"fu-th-content\">\r\n @if (col.columnHeaderTemplate()) {\r\n <ng-container\r\n [ngTemplateOutlet]=\"col.columnHeaderTemplate()!\"\r\n [ngTemplateOutletContext]=\"{ index: $index }\"\r\n />\r\n @if (col.sortable()) {\r\n <ng-container\r\n [ngTemplateOutlet]=\"defaultSortIcon\"\r\n [ngTemplateOutletContext]=\"{\r\n $implicit: col.sortDirection(),\r\n }\"\r\n />\r\n }\r\n } @else {\r\n <span class=\"fu-header-main\">\r\n {{ col.header() }}\r\n\r\n @if (col.sortable()) {\r\n <ng-container\r\n [ngTemplateOutlet]=\"defaultSortIcon\"\r\n [ngTemplateOutletContext]=\"{\r\n $implicit: col.sortDirection(),\r\n }\"\r\n />\r\n }\r\n </span>\r\n }\r\n <!-- -->\r\n @if (col.columnHeaderAddonTemplate()) {\r\n <span class=\"fu-header-addon\">\r\n <ng-container\r\n [ngTemplateOutlet]=\"col.columnHeaderAddonTemplate()!\"\r\n [ngTemplateOutletContext]=\"{ index: $index }\"\r\n />\r\n </span>\r\n }\r\n </div>\r\n </th>\r\n }\r\n </tr>\r\n </thead>\r\n\r\n <tbody>\r\n @if (loading() && pendingSaveRowIndex() === null) {\r\n <ng-container [ngTemplateOutlet]=\"loadingTemplate\" />\r\n } @else {\r\n <!-- -->\r\n @for (row of visibleRows(); let rowIndex = $index; track rowIndex) {\r\n <tr\r\n #contextTrigger=\"cdkContextMenuTriggerFor\"\r\n [cdkContextMenuTriggerFor]=\"rowContextMenu\"\r\n [cdkContextMenuTriggerData]=\"{\r\n row,\r\n index: rowIndex,\r\n trigger: contextTrigger,\r\n }\"\r\n [cdkContextMenuDisabled]=\"!rowContextMenuTemplate()\"\r\n [class.fu-cell-success]=\"\r\n rowStates.get(rowIndex) === 'success' && !loading()\r\n \"\r\n [class.fu-cell-error]=\"\r\n rowStates.get(rowIndex) === 'error' && !loading()\r\n \"\r\n [class.fu-row-expanded]=\"expandIndex() === rowIndex\"\r\n [attr.data-row-index]=\"rowIndex\"\r\n [class]=\"rowClass() ? rowClass()!(row, rowIndex) : ''\"\r\n (animationend)=\"onEditAnimationEnd()\"\r\n (click)=\"onRowClick(row, $event)\"\r\n >\r\n @if (expandRowTemplate()) {\r\n <td\r\n class=\"fu-expand-cell\"\r\n [attr.data-expand]=\"expandIndex() === rowIndex\"\r\n (click)=\"toggleExpand(rowIndex, $event)\"\r\n >\r\n <span class=\"fu-icon\">\r\n <svg\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n width=\"24\"\r\n height=\"24\"\r\n viewBox=\"0 0 24 24\"\r\n >\r\n <path\r\n fill=\"currentColor\"\r\n d=\"M10 6L8.59 7.41L13.17 12l-4.58 4.59L10 18l6-6z\"\r\n />\r\n </svg>\r\n </span>\r\n </td>\r\n }\r\n <!-- -->\r\n @for (\r\n col of visibleColumns();\r\n let colIndex = $index;\r\n track colIndex\r\n ) {\r\n @let isMatch = isActiveMatch(rowIndex, col);\r\n\r\n <td\r\n [class.fu-cell-pending]=\"\r\n rowStates.get(rowIndex) === 'pending' && loading()\r\n \"\r\n [class.fu-cell-editing]=\"editingRowIndex() === rowIndex\"\r\n [class.fu-cell-sorting]=\"\r\n col.sortable() && col.sortDirection() !== null\r\n \"\r\n [class.fu-active-match]=\"isMatch\"\r\n >\r\n <div class=\"fu-header-text\">\r\n {{ col.header() }}\r\n </div>\r\n\r\n @if (col.columnCellTemplate()) {\r\n <ng-container\r\n [ngTemplateOutlet]=\"col.columnCellTemplate()!\"\r\n [ngTemplateOutletContext]=\"{\r\n $implicit: getCellValue(row, col.field()),\r\n row: row,\r\n index: rowIndex,\r\n searchTerm: searchTerm(),\r\n activeMatch: isMatch,\r\n }\"\r\n }\r\n />\r\n } @else if (col.columnActionsTemplate()) {\r\n <ng-container\r\n [ngTemplateOutlet]=\"col.columnActionsTemplate()!\"\r\n [ngTemplateOutletContext]=\"{\r\n row: row,\r\n index: rowIndex,\r\n editing: editingRowIndex() === rowIndex,\r\n startEdit: startEditRow.bind(this),\r\n saveEdit: saveEditRow.bind(this),\r\n cancelEdit: cancelEditRow.bind(this),\r\n toggleExpand: toggleExpand.bind(this),\r\n }\"\r\n }\r\n />\r\n } @else {\r\n <fu-cell\r\n [type]=\"col.type()\"\r\n [searchTerm]=\"searchTerm()\"\r\n [editable]=\"col.editable()\"\r\n [value]=\"getCellValue(row, col.field())\"\r\n [activeMatch]=\"isMatch\"\r\n [editing]=\"editingRowIndex() === rowIndex\"\r\n (cellValueChange)=\"onCellChange(row, col, $event)\"\r\n />\r\n }\r\n </td>\r\n }\r\n </tr>\r\n\r\n @if (expandIndex() === rowIndex) {\r\n <tr>\r\n <td [attr.colspan]=\"columns().length + 1\">\r\n <ng-container\r\n [ngTemplateOutlet]=\"expandRowTemplate()!\"\r\n [ngTemplateOutletContext]=\"{\r\n $implicit: row,\r\n index: rowIndex,\r\n searchTerm: searchTerm,\r\n }\"\r\n />\r\n </td>\r\n </tr>\r\n }\r\n } @empty {\r\n <tr class=\"fu-no-data-row\">\r\n <td [attr.colspan]=\"columns().length + 1\">\r\n <ng-container\r\n [ngTemplateOutlet]=\"noDataTemplate() ?? defaultNoData\"\r\n />\r\n </td>\r\n </tr>\r\n }\r\n }\r\n </tbody>\r\n </table>\r\n </div>\r\n\r\n @if (configPanel() || sidebarTemplate()) {\r\n <div\r\n class=\"fu-sidebar\"\r\n [class.fu-open]=\"isSidebarOpen()\"\r\n (fuClickOutside)=\"activeSidebarPanel.set(null)\"\r\n >\r\n @if (isSidebarOpen()) {\r\n <div class=\"fu-content\">\r\n @if (isColumnsOpen()) {\r\n <ng-container [ngTemplateOutlet]=\"defaultSidebar\" />\r\n } @else if (isLayoutOpen()) {\r\n <ng-container [ngTemplateOutlet]=\"configSidebar\" />\r\n } @else if (isCustomOpen()) {\r\n <ng-container [ngTemplateOutlet]=\"sidebarTemplate()!\" />\r\n }\r\n </div>\r\n }\r\n\r\n <div class=\"fu-handle\">\r\n @if (configPanel()) {\r\n <button\r\n fuButton\r\n variant=\"icon\"\r\n severity=\"success\"\r\n (click)=\"toggleColumnsPanel()\"\r\n >\r\n <svg\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n width=\"24\"\r\n height=\"24\"\r\n viewBox=\"0 0 24 24\"\r\n >\r\n <path\r\n fill=\"currentColor\"\r\n d=\"M3.25 3a.75.75 0 0 0 0 1.5h.5C4.44 4.5 5 5.06 5 5.75v12.5c0 .69-.56 1.25-1.25 1.25h-.5a.75.75 0 0 0 0 1.5h.5a2.75 2.75 0 0 0 2.75-2.75V5.75A2.75 2.75 0 0 0 3.75 3zm7.5 0A2.75 2.75 0 0 0 8 5.75v12.5A2.75 2.75 0 0 0 10.75 21h.415l.356-1.423l.02-.077h-.791c-.69 0-1.25-.56-1.25-1.25V5.75c0-.69.56-1.25 1.25-1.25h2.5c.69 0 1.25.56 1.25 1.25v10.105l1.5-1.5V5.75A2.75 2.75 0 0 0 13.25 3zM19 11.483q-.325.198-.607.48l-.893.892V5.75A2.75 2.75 0 0 1 20.25 3h.5a.75.75 0 0 1 0 1.5h-.5c-.69 0-1.25.56-1.25 1.25zm.1 1.186l-5.903 5.903a2.7 2.7 0 0 0-.706 1.247l-.458 1.831a1.087 1.087 0 0 0 1.319 1.318l1.83-.457a2.7 2.7 0 0 0 1.248-.707l5.902-5.902A2.286 2.286 0 0 0 19.1 12.67\"\r\n />\r\n </svg>\r\n </button>\r\n <button\r\n fuButton\r\n variant=\"icon\"\r\n severity=\"warning\"\r\n (click)=\"toggleLayoutPanel()\"\r\n >\r\n <svg\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n width=\"24\"\r\n height=\"24\"\r\n viewBox=\"0 0 24 24\"\r\n >\r\n <path\r\n fill=\"currentColor\"\r\n d=\"M21 13.1c-.1 0-.3.1-.4.2l-1 1l2.1 2.1l1-1c.2-.2.2-.6 0-.8l-1.3-1.3c-.1-.1-.2-.2-.4-.2m-1.9 1.8l-6.1 6V23h2.1l6.1-6.1zM21 3h-8v6h8zm-2 4h-4V5h4zm-6 11.06V11h8v.1c-.76 0-1.43.4-1.81.79L18.07 13H15v3.07zM11 3H3v10h8zm-2 8H5V5h4zm2 9.06V15H3v6h8zM9 19H5v-2h4z\"\r\n />\r\n </svg>\r\n </button>\r\n }\r\n @if (sidebarTemplate()) {\r\n <button\r\n fuButton\r\n variant=\"icon\"\r\n severity=\"danger\"\r\n (click)=\"toggleCustomPanel()\"\r\n >\r\n <svg\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n width=\"24\"\r\n height=\"24\"\r\n viewBox=\"0 0 24 24\"\r\n >\r\n <path\r\n fill=\"currentColor\"\r\n d=\"M12 16c1.1 0 2 .9 2 2s-.9 2-2 2s-2-.9-2-2s.9-2 2-2m0-6c1.1 0 2 .9 2 2s-.9 2-2 2s-2-.9-2-2s.9-2 2-2m0-6c1.1 0 2 .9 2 2s-.9 2-2 2s-2-.9-2-2s.9-2 2-2M6 16c1.1 0 2 .9 2 2s-.9 2-2 2s-2-.9-2-2s.9-2 2-2m0-6c1.1 0 2 .9 2 2s-.9 2-2 2s-2-.9-2-2s.9-2 2-2m0-6c1.1 0 2 .9 2 2s-.9 2-2 2s-2-.9-2-2s.9-2 2-2m12 12c1.1 0 2 .9 2 2s-.9 2-2 2s-2-.9-2-2s.9-2 2-2m0-6c1.1 0 2 .9 2 2s-.9 2-2 2s-2-.9-2-2s.9-2 2-2m0-6c1.1 0 2 .9 2 2s-.9 2-2 2s-2-.9-2-2s.9-2 2-2\"\r\n />\r\n </svg>\r\n </button>\r\n }\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n\r\n @if (paginator()) {\r\n <div class=\"fu-paginator\">\r\n <div class=\"fu-paginator-entries\">\r\n {{ entriesLabel() }}\r\n </div>\r\n\r\n <button\r\n fuButton\r\n variant=\"icon\"\r\n (click)=\"prevPage()\"\r\n [disabled]=\"pageIndex() === 0 || disablePaginator()\"\r\n >\r\n <svg\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n width=\"24\"\r\n height=\"24\"\r\n viewBox=\"0 0 24 24\"\r\n >\r\n <path\r\n fill=\"none\"\r\n stroke=\"currentColor\"\r\n stroke-linecap=\"round\"\r\n stroke-linejoin=\"round\"\r\n stroke-width=\"1.5\"\r\n d=\"m14 7l-5 5l5 5\"\r\n />\r\n </svg>\r\n </button>\r\n\r\n <span class=\"fu-page-info\">\r\n {{ intl.pageLabel() }} {{ pageIndex() + 1 }} {{ intl.ofLabel() }}\r\n {{ totalPages() }}\r\n </span>\r\n\r\n <button\r\n fuButton\r\n variant=\"icon\"\r\n (click)=\"nextPage()\"\r\n [disabled]=\"pageIndex() >= totalPages() - 1 || disablePaginator()\"\r\n >\r\n <svg\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n width=\"24\"\r\n height=\"24\"\r\n viewBox=\"0 0 24 24\"\r\n >\r\n <path\r\n fill=\"none\"\r\n stroke=\"currentColor\"\r\n stroke-linecap=\"round\"\r\n stroke-linejoin=\"round\"\r\n stroke-width=\"1.5\"\r\n d=\"m10 17l5-5l-5-5\"\r\n />\r\n </svg>\r\n </button>\r\n\r\n <select [(ngModel)]=\"pageSize\" (ngModelChange)=\"onPageSizeChange($event)\">\r\n @for (size of pageSizeOptions(); track size) {\r\n <option class=\"fu-option\" [value]=\"size\">{{ size }}</option>\r\n }\r\n </select>\r\n </div>\r\n }\r\n</div>\r\n\r\n<ng-template\r\n #rowContextMenu\r\n let-row=\"row\"\r\n let-index=\"index\"\r\n let-trigger=\"trigger\"\r\n>\r\n <div cdkMenu>\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n rowContextMenuTemplate()!;\r\n context: { row, index, trigger }\r\n \"\r\n />\r\n </div>\r\n</ng-template>\r\n\r\n<ng-template #loadingTemplate>\r\n @for (\r\n i of [].constructor(skeletonRowCount());\r\n let rowIndex = $index;\r\n track rowIndex\r\n ) {\r\n <tr class=\"fu-skeleton-row\">\r\n @for (\r\n j of [].constructor(skeletonColumnCount());\r\n let colIndex = $index;\r\n track colIndex\r\n ) {\r\n <td>\r\n <div class=\"fu-skeleton-cell\"></div>\r\n </td>\r\n }\r\n </tr>\r\n }\r\n</ng-template>\r\n\r\n<ng-template #defaultSortIcon let-direction>\r\n <span class=\"fu-sort-icon\">\r\n @if (!direction) {\r\n <svg\r\n class=\"fu-sort-icon-default\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n width=\"16\"\r\n height=\"16\"\r\n viewBox=\"0 0 24 24\"\r\n >\r\n <path\r\n fill=\"none\"\r\n stroke=\"currentColor\"\r\n stroke-linecap=\"round\"\r\n stroke-linejoin=\"round\"\r\n stroke-width=\"2\"\r\n d=\"m3 9l4-4l4 4M7 5v14m14-4l-4 4l-4-4m4 4V5\"\r\n />\r\n </svg>\r\n }\r\n\r\n @if (direction === 'asc') {\r\n <svg\r\n class=\"fu-sort-icon-active\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n width=\"16\"\r\n height=\"16\"\r\n viewBox=\"0 0 24 24\"\r\n >\r\n <path\r\n fill=\"none\"\r\n stroke=\"currentColor\"\r\n stroke-linecap=\"round\"\r\n stroke-linejoin=\"round\"\r\n stroke-width=\"2\"\r\n d=\"M4 6h7m-7 6h7m-7 6h9m2-9l3-3l3 3m-3-3v12\"\r\n />\r\n </svg>\r\n }\r\n\r\n @if (direction === 'desc') {\r\n <svg\r\n class=\"fu-sort-icon-active\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n width=\"16\"\r\n height=\"16\"\r\n viewBox=\"0 0 24 24\"\r\n >\r\n <path\r\n fill=\"none\"\r\n stroke=\"currentColor\"\r\n stroke-linecap=\"round\"\r\n stroke-linejoin=\"round\"\r\n stroke-width=\"2\"\r\n d=\"M4 6h9m-9 6h7m-7 6h7m4-3l3 3l3-3m-3-9v12\"\r\n />\r\n </svg>\r\n }\r\n </span>\r\n</ng-template>\r\n\r\n<ng-template #defaultNoData>\r\n <div class=\"fu-no-data-default\">{{ intl.noData() }}</div>\r\n</ng-template>\r\n\r\n<ng-template #configSidebar>\r\n <div class=\"fu-config-sidebar\">\r\n <div class=\"fu-config-item\">\r\n <label for=\"responsive\">{{ intl.responsiveLabel() }}</label>\r\n <input id=\"responsive\" type=\"checkbox\" [(ngModel)]=\"responsiveConfig\" />\r\n </div>\r\n\r\n <div class=\"fu-config-item\">\r\n <label for=\"scrollable\">{{ intl.scrollableLabel() }}</label>\r\n <input id=\"scrollable\" type=\"checkbox\" [(ngModel)]=\"scrollableConfig\" />\r\n </div>\r\n\r\n <div class=\"fu-config-item\">\r\n <label for=\"stickyHeader\">{{ intl.lockHeaderLabel() }}</label>\r\n <input\r\n id=\"stickyHeader\"\r\n type=\"checkbox\"\r\n [(ngModel)]=\"stickyHeaderConfig\"\r\n [disabled]=\"!scrollableConfig()\"\r\n />\r\n </div>\r\n\r\n <div class=\"fu-config-item\">\r\n <label for=\"stickyFirstColumn\">{{ intl.lockLeftLabel() }}</label>\r\n <input\r\n id=\"stickyFirstColumn\"\r\n type=\"checkbox\"\r\n [(ngModel)]=\"stickyFirstColumnConfig\"\r\n [disabled]=\"!scrollableConfig()\"\r\n />\r\n </div>\r\n\r\n <div class=\"fu-config-item\">\r\n <label for=\"stickyLastColumn\">{{ intl.lockRightLabel() }}</label>\r\n <input\r\n id=\"stickyLastColumn\"\r\n type=\"checkbox\"\r\n [(ngModel)]=\"stickyLastColumnConfig\"\r\n [disabled]=\"!scrollableConfig()\"\r\n />\r\n </div>\r\n </div>\r\n</ng-template>\r\n\r\n<ng-template #defaultSidebar>\r\n <div\r\n class=\"fu-columns-panel\"\r\n cdkDropList\r\n cdkDropListOrientation=\"vertical\"\r\n (cdkDropListDropped)=\"drop($event)\"\r\n >\r\n <div class=\"fu-drag-lock\">\r\n @for (col of columns(); track col.field()) {\r\n <div\r\n class=\"fu-column-item\"\r\n cdkDrag\r\n cdkDragLockAxis=\"y\"\r\n cdkDragBoundary=\".fu-drag-lock\"\r\n [cdkDragDisabled]=\"!col.reorderable()\"\r\n >\r\n <span class=\"fu-drag-handle\" cdkDragHandle>\u2630</span>\r\n\r\n <span class=\"fu-label\">{{ col.header() }}</span>\r\n\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"col.visible()\"\r\n [disabled]=\"\r\n !col.hideable() || (visibleCount() === 3 && col.visible())\r\n \"\r\n (change)=\"toggleVisibility(col)\"\r\n />\r\n\r\n <ng-template cdkDragPreview>\r\n <div class=\"fu-drag-preview\">\r\n <svg\r\n class=\"fu-drag-icon\"\r\n width=\"16px\"\r\n height=\"16px\"\r\n fill=\"currentColor\"\r\n viewBox=\"0 0 25 25\"\r\n >\r\n <path\r\n d=\"M10 9h4V6h3l-5-5-5 5h3v3zm-1 1H6V7l-5 5 5 5v-3h3v-4zm14 2l-5-5v3h-3v4h3v3l5-5zm-9 3h-4v3H7l5 5 5-5h-3v-3z\"\r\n ></path>\r\n </svg>\r\n <div class=\"fu-drag-label\">{{ col.header() }}</div>\r\n </div>\r\n </ng-template>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n</ng-template>\r\n", styles: [".fu-table-wrapper{--fu-table-bg: oklch(from var(--fu-surface) l c h);--fu-table-text: oklch(from var(--fu-text) l c h);--fu-table-border: oklch(from var(--fu-border) l c h);--fu-table-input-bg: oklch(from var(--fu-surface) calc(l - .03) c h);--fu-table-header-bg: oklch(from var(--fu-surface) calc(l - .04) c h);--fu-table-header-text: oklch(from var(--fu-text) l c h);--fu-table-header-border: oklch(from var(--fu-border) calc(l - .02) c h);--fu-table-row-bg: oklch(from var(--fu-surface) l c h);--fu-table-row-hover-bg: oklch(from var(--fu-surface) calc(l - .035) c h);--fu-table-row-border: oklch(from var(--fu-border) l c h);--fu-table-row-striped-bg: oklch(from var(--fu-surface) calc(l - .02) c h);--fu-table-row-active-bg: oklch( from var(--fu-accent) calc(l + .3) calc(c/5) h );--fu-table-row-pending-bg: oklch( from var(--fu-warning) calc(l + .25) calc(c/4) h );--fu-table-sticky-shadow: color-mix( in oklch, oklch(from var(--fu-text) .2 0 0) 30%, transparent );--fu-table-sticky-bg: oklch(from var(--fu-surface) calc(l - .05) c h);--fu-table-sticky-border: oklch(from var(--fu-border) calc(l - .08) c h);--fu-skeleton-base: color-mix( in srgb, var(--fu-table-row-bg) 90%, var(--fu-text) 10% );--fu-skeleton-highlight: color-mix( in srgb, var(--fu-table-row-bg) 70%, var(--fu-text) 30% );margin:16px;border:2px solid var(--fu-table-border);font-family:var(--fu-font-family);font-size:var(--fu-font-size)}.fu-table-scroll.fu-scrollable{width:100%;overflow-x:auto;overflow-y:auto}.fu-table-scroll,.fu-table{width:100%}.fu-scrollable{overflow-x:auto;overflow-y:auto}.fu-table-scrollable{width:max-content;min-width:100%;table-layout:auto}.fu-sticky-header thead th{position:sticky;top:0;z-index:20;background:var(--fu-table-sticky-bg);border-bottom:1px solid var(--fu-table-sticky-border)}.fu-sticky-first th:first-child,.fu-sticky-first td:first-child{position:sticky;left:0;z-index:10;background:var(--fu-table-sticky-bg);border-right:1px solid var(--fu-table-sticky-border)}.fu-sticky-header.fu-sticky-first thead th:first-child{z-index:30}.fu-sticky-last th:last-child,.fu-sticky-last td:last-child{position:sticky;right:0;z-index:10;background:var(--fu-table-sticky-bg);border-left:1px solid var(--fu-table-sticky-border)}.fu-sticky-header.fu-sticky-last thead th:last-child{z-index:30}.fu-sticky-first td:first-child{box-shadow:4px 0 6px -2px var(--fu-table-sticky-shadow)}.fu-sticky-last td:last-child{box-shadow:-4px 0 6px -2px var(--fu-table-sticky-shadow)}.fu-sticky-header thead th{box-shadow:0 4px 6px -2px var(--fu-table-sticky-shadow)}table{border-collapse:separate;border-spacing:0}tr{height:42px}th,td{padding:0 8px;background-color:var(--fu-table-bg);color:var(--fu-table-text);border-right:1px solid var(--fu-table-border);border-bottom:1px solid var(--fu-table-border);vertical-align:middle}th{text-align:left;vertical-align:middle}td .fu-icon{display:flex;justify-content:center;align-items:center;transition:transform .15s cubic-bezier(.4,0,.2,1);transform-origin:center}td[data-expand=true] .fu-icon{transform:rotate(90deg)}thead th{background-color:var(--fu-table-header-bg);border-top:2px solid var(--fu-table-border)}tr:nth-child(2n) td{background-color:var(--fu-table-row-striped-bg)}tbody tr:hover td{background-color:var(--fu-table-row-hover-bg)}th.fu-sortable{cursor:pointer;-webkit-user-select:none;user-select:none}th.fu-sortable:hover{background-color:var(--fu-table-row-hover-bg)}tbody td.fu-cell-sorting{font-weight:600}.fu-th-content{display:flex;align-items:center;gap:8px}.fu-header-main{flex:1;display:inline-flex;align-items:center;gap:4px}.fu-header-addon{display:inline-flex;align-items:center;border-color:var(--fu-table-sticky-border)}.fu-paginator{display:flex;align-items:center;justify-content:flex-end;gap:12px;padding:8px 16px;font-size:14px;background-color:var(--fu-table-bg);color:var(--fu-table-text)}.fu-page-info{white-space:nowrap;color:var(--fu-table-text)}.fu-paginator select{height:32px;padding:4px 8px;border:1px solid var(--fu-table-border);border-radius:0;background-color:var(--fu-table-row-striped-bg);cursor:pointer;color:var(--fu-table-text)}.fu-paginator select:focus{border-color:var(--fu-table-sticky-border);outline:none}.fu-search{display:flex;align-items:center;padding:8px 16px;background-color:var(--fu-table-bg);gap:16px}.fu-search input[type=search]{width:25%;max-width:100%;padding:6px 10px;font-size:14px;line-height:1.4;border:1px solid var(--fu-table-border);border-radius:var(--fu-border-radius);background-color:var(--fu-table-row-striped-bg);color:var(--fu-table-text);outline:none;transition:border-color .15s,box-shadow .15s}.fu-search input[type=search]::placeholder{color:#999}.fu-search input[type=search]:focus{border-color:var(--fu-table-sticky-border)}tbody tr.fu-no-data-row td{text-align:center;padding:24px;background-color:var(--fu-table-bg);color:var(--fu-table-text);border-right-color:var(--fu-table-border);height:207px;pointer-events:none}.fu-no-data-default{font-style:italic}.fu-skeleton-row td{padding:10px 8px}.fu-skeleton-cell{height:20px;width:100%;border-radius:4px;background-image:linear-gradient(90deg,var(--fu-skeleton-base) 25%,var(--fu-skeleton-highlight) 37%,var(--fu-skeleton-base) 63%);background-size:400% 100%;animation:skeleton-loading 1.4s ease infinite}@media (prefers-reduced-motion: reduce){.fu-skeleton-cell{animation:none;opacity:.6}}@keyframes skeleton-loading{0%{background-position:100% 50%}to{background-position:0 50%}}tbody tr.fu-cell-error td{animation:flash-error 1.6s ease-out}@keyframes flash-error{0%{background-color:#fecaca}to{background-color:var(--fu-table-bg)}}tbody tr.fu-cell-success td{animation:flash-success 1.6s ease-out}@keyframes flash-success{0%{background-color:#98efa9}to{background-color:var(--fu-table-bg)}}tbody tr td.fu-cell-pending{pointer-events:none;animation:flash-pending 1.4s ease-in-out infinite}@keyframes flash-pending{0%,to{background-color:var(--fu-table-row-bg)}50%{background-color:var(--fu-table-row-pending-bg)}}.fu-table-layout{position:relative}.fu-table-layout.fu-with-sidebar{padding-right:43px}.fu-sidebar{position:absolute;right:0;bottom:0;top:0;width:41.5px;transition:width .25s ease;background-color:var(--fu-table-bg);border:2px solid var(--fu-table-border);border-right:none;overflow:hidden;display:grid;z-index:40}.fu-sidebar.fu-open{min-width:230px}.fu-handle{position:absolute;right:0;background-color:var(--fu-table-bg);height:100%;display:flex;flex-direction:column;align-items:center;gap:8px;padding:4px}.fu-content{border-right:2px solid var(--fu-table-border);overflow-y:auto;width:189px}.fu-columns-panel{display:flex;flex-direction:column;margin:4px 4px 0}.fu-column-item{display:flex;align-items:center;gap:8px;padding:6px 8px;margin-bottom:4px;background:var(--fu-table-header-bg);border:1px solid var(--fu-table-border);border-radius:var(--fu-border-radius)}.fu-column-item:hover{background:var(--fu-table-row-hover-bg)}.fu-column-item .fu-drag-handle{cursor:grab}.fu-config-item{background-color:var(--fu-table-header-bg);border:1px solid var(--fu-table-border)}.fu-config-item label{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fu-header-text{display:none}@media (max-width: 900px){.fu-table--responsive thead{display:none}.fu-table--responsive tbody{display:block}.fu-table--responsive tr{display:block;height:auto;margin-bottom:4px;border-top:2px solid var(--fu-table-border)}.fu-table--responsive tr.fu-row-expanded{margin-bottom:0}.fu-table--responsive th{display:block}.fu-table--responsive td{display:flex;align-items:center;justify-content:space-between;height:42px;border-bottom:1px solid var(--fu-table-border)}.fu-table--responsive .fu-header-text{display:block}}.fu-outline-1{outline:1px solid var(--fu-table-border)}.fu-expand-cell{color:var(--fu-accent);cursor:pointer}.fu-search-nav{display:flex;gap:8px}.fu-match-counter{display:flex;align-items:center;margin-left:8px}.fu-expand-header{width:40px}.fu-header-addon{border-left:1px solid var(--fu-table-sticky-border);padding-left:8px;margin-left:8px}.fu-paginator-entries{margin-right:auto}.fu-sort-icon{margin-top:4px}.fu-sort-icon-default{color:#c4cbd5}.fu-config-sidebar{display:flex;flex-direction:column;gap:4px;padding:4px}.fu-config-item{display:flex;gap:8px;justify-content:space-between;padding:8px;background-color:var(--fu-table-header-bg)}.fu-config-item:hover{background-color:var(--fu-table-row-hover-bg)}.fu-label{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fu-drag-preview{display:flex;align-items:center;background-color:var(--fu-table-header-bg);border:1px solid var(--fu-table-border);border-radius:var(--fu-border-radius);box-shadow:0 4px 6px -2px var(--fu-table-sticky-shadow)}.fu-drag-icon{margin:0 8px}.fu-drag-label{padding:8px 12px}.fu-context-menu{background:var(--fu-table-bg);border:1px solid var(--fu-table-border);box-shadow:0 2px 5px #00000059;display:inline-flex;flex-direction:column;min-width:180px;max-width:280px;padding:6px 0}::-webkit-scrollbar{width:8px;height:8px}::-webkit-scrollbar-track{background:var(--fu-table-bg);border-radius:10px}::-webkit-scrollbar-thumb{background:var(--fu-table-border);border-radius:10px}::-webkit-scrollbar-thumb:hover{background:#9ca3af}*{scrollbar-width:thin;scrollbar-color:var(--fu-table-border) var(--fu-table-bg)}\n"] }]
|
|
2551
|
+
}], ctorParameters: () => [{ type: i0.Injector }] });
|
|
2552
|
+
|
|
2553
|
+
/**
|
|
2554
|
+
* Defines a custom cell template for a `fu-column`.
|
|
2555
|
+
*
|
|
2556
|
+
* The template context exposes:
|
|
2557
|
+
* - `value` – the current cell value
|
|
2558
|
+
* - `row` – the current row data
|
|
2559
|
+
* - `index` – row index
|
|
2560
|
+
* - `searchTerm` – current search term
|
|
2561
|
+
* - `activeMatch` – whether this cell matches the active search match
|
|
2562
|
+
*
|
|
2563
|
+
* ## Typing
|
|
2564
|
+
* To enable strong typing for `row`, provide the table data using `rowOf`.
|
|
2565
|
+
* This is used only for type inference and does not affect rendering.
|
|
2566
|
+
*
|
|
2567
|
+
* Usage:
|
|
2568
|
+
* ```html
|
|
2569
|
+
* <ng-template fuColumnCell let-value let-row="row" let-index="index">
|
|
2570
|
+
* {{ value }}
|
|
2571
|
+
* </ng-template>
|
|
2572
|
+
* ```
|
|
2573
|
+
*
|
|
2574
|
+
* If `rowOf` is omitted, `row` will be typed as `any`.
|
|
2575
|
+
*/
|
|
2576
|
+
class FuColumnCellTemplateDirective {
|
|
2577
|
+
constructor() {
|
|
2578
|
+
/**
|
|
2579
|
+
* Provides typing context for the column cell template.
|
|
2580
|
+
* Used only for type inference – not consumed at runtime.
|
|
2581
|
+
*/
|
|
2582
|
+
this.of = input(null, {
|
|
2583
|
+
alias: 'rowOf',
|
|
2584
|
+
});
|
|
2585
|
+
}
|
|
2586
|
+
static ngTemplateContextGuard(_dir, ctx) {
|
|
2587
|
+
return true;
|
|
2588
|
+
}
|
|
2589
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.6", ngImport: i0, type: FuColumnCellTemplateDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
2590
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "19.0.6", type: FuColumnCellTemplateDirective, isStandalone: true, selector: "[fuColumnCell]", inputs: { of: { classPropertyName: "of", publicName: "rowOf", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 }); }
|
|
2591
|
+
}
|
|
2592
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.6", ngImport: i0, type: FuColumnCellTemplateDirective, decorators: [{
|
|
2593
|
+
type: Directive,
|
|
2594
|
+
args: [{
|
|
2595
|
+
selector: '[fuColumnCell]',
|
|
2596
|
+
}]
|
|
2597
|
+
}] });
|
|
2598
|
+
|
|
2599
|
+
/**
|
|
2600
|
+
* Defines a custom header template for a `fu-column` header.
|
|
2601
|
+
*
|
|
2602
|
+
* Fully replaces default header content for a `fu-column` header.
|
|
2603
|
+
*
|
|
2604
|
+
* The template context exposes:
|
|
2605
|
+
* - `index` – current column index
|
|
2606
|
+
*
|
|
2607
|
+
* Usage:
|
|
2608
|
+
* ```html
|
|
2609
|
+
* <ng-template fuColumnHeader let-index="index">
|
|
2610
|
+
* {{ index }}
|
|
2611
|
+
* </ng-template>
|
|
2612
|
+
* ```
|
|
2613
|
+
*/
|
|
2614
|
+
class FuColumnHeaderTemplateDirective {
|
|
2615
|
+
static ngTemplateContextGuard(_dir, ctx) {
|
|
2616
|
+
return true;
|
|
2617
|
+
}
|
|
2618
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.6", ngImport: i0, type: FuColumnHeaderTemplateDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
2619
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.0.6", type: FuColumnHeaderTemplateDirective, isStandalone: true, selector: "[fuColumnHeader]", ngImport: i0 }); }
|
|
2620
|
+
}
|
|
2621
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.6", ngImport: i0, type: FuColumnHeaderTemplateDirective, decorators: [{
|
|
2622
|
+
type: Directive,
|
|
2623
|
+
args: [{
|
|
2624
|
+
selector: '[fuColumnHeader]',
|
|
2625
|
+
}]
|
|
2626
|
+
}] });
|
|
2627
|
+
|
|
2628
|
+
/**
|
|
2629
|
+
* Defines a custom header addon template for a `fu-column` header.
|
|
2630
|
+
*
|
|
2631
|
+
* Adds content to right side in default header for a `fu-column` header.
|
|
2632
|
+
*
|
|
2633
|
+
* The template context exposes:
|
|
2634
|
+
* - `index` – current column index
|
|
2635
|
+
*
|
|
2636
|
+
* Usage:
|
|
2637
|
+
* ```html
|
|
2638
|
+
* <ng-template fuColumnHeaderAddon let-index="index">
|
|
2639
|
+
* {{ index }}
|
|
2640
|
+
* </ng-template>
|
|
2641
|
+
* ```
|
|
2642
|
+
*/
|
|
2643
|
+
class FuColumnHeaderAddonTemplateDirective {
|
|
2644
|
+
static ngTemplateContextGuard(_dir, ctx) {
|
|
2645
|
+
return true;
|
|
2646
|
+
}
|
|
2647
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.6", ngImport: i0, type: FuColumnHeaderAddonTemplateDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
2648
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.0.6", type: FuColumnHeaderAddonTemplateDirective, isStandalone: true, selector: "[fuColumnHeaderAddon]", ngImport: i0 }); }
|
|
2649
|
+
}
|
|
2650
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.6", ngImport: i0, type: FuColumnHeaderAddonTemplateDirective, decorators: [{
|
|
2651
|
+
type: Directive,
|
|
2652
|
+
args: [{
|
|
2653
|
+
selector: '[fuColumnHeaderAddon]',
|
|
2654
|
+
}]
|
|
2655
|
+
}] });
|
|
2656
|
+
|
|
2657
|
+
/**
|
|
2658
|
+
* Defines a custom action template for a `fu-column`.
|
|
2659
|
+
*
|
|
2660
|
+
* This template is intended for rendering row-level actions
|
|
2661
|
+
* such as edit, save, cancel, or expand controls.
|
|
2662
|
+
*
|
|
2663
|
+
* The template context exposes:
|
|
2664
|
+
* - `row` – the current row data
|
|
2665
|
+
* - `index` – row index
|
|
2666
|
+
* - `editing` – whether the row is currently in edit mode
|
|
2667
|
+
* - `startEdit`, `saveEdit`, `cancelEdit` – row edit helpers
|
|
2668
|
+
* - `toggleExpand` – expands or collapses the row
|
|
2669
|
+
*
|
|
2670
|
+
* ## Event handling
|
|
2671
|
+
* If your table listens to row click events, interactive elements
|
|
2672
|
+
* inside this template should pass `$event` to helpers in order to
|
|
2673
|
+
* stop event propagation:
|
|
2674
|
+
*
|
|
2675
|
+
* ```html
|
|
2676
|
+
* <button (click)="startEdit(index, $event)">Edit</button>
|
|
2677
|
+
* ```
|
|
2678
|
+
*
|
|
2679
|
+
* ## Typing
|
|
2680
|
+
* To enable strong typing for `row`, provide the table data using `rowOf`.
|
|
2681
|
+
* This input is used only for type inference and does not affect rendering.
|
|
2682
|
+
*
|
|
2683
|
+
* ## Usage
|
|
2684
|
+
* ```html
|
|
2685
|
+
* <ng-template fuColumnActions let-index="index" let-startEdit="startEdit">
|
|
2686
|
+
* <button (click)="startEdit(index, $event)">✏️</button>
|
|
2687
|
+
* </ng-template>
|
|
2688
|
+
* ```
|
|
2689
|
+
*
|
|
2690
|
+
* * If `rowOf` is omitted, `row` will be typed as `any`.
|
|
2691
|
+
*/
|
|
2692
|
+
class FuColumnActionsTemplateDirective {
|
|
2693
|
+
constructor() {
|
|
2694
|
+
/**
|
|
2695
|
+
* Provides typing context for the column cell template.
|
|
2696
|
+
* Used only for type inference – not consumed at runtime.
|
|
2697
|
+
*/
|
|
2698
|
+
this.of = input(null, {
|
|
2699
|
+
alias: 'rowOf',
|
|
2700
|
+
});
|
|
2701
|
+
}
|
|
2702
|
+
static ngTemplateContextGuard(_dir, ctx) {
|
|
2703
|
+
return true;
|
|
2704
|
+
}
|
|
2705
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.6", ngImport: i0, type: FuColumnActionsTemplateDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
2706
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "19.0.6", type: FuColumnActionsTemplateDirective, isStandalone: true, selector: "[fuColumnActions]", inputs: { of: { classPropertyName: "of", publicName: "rowOf", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 }); }
|
|
2707
|
+
}
|
|
2708
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.6", ngImport: i0, type: FuColumnActionsTemplateDirective, decorators: [{
|
|
2709
|
+
type: Directive,
|
|
2710
|
+
args: [{
|
|
2711
|
+
selector: '[fuColumnActions]',
|
|
2712
|
+
}]
|
|
2713
|
+
}] });
|
|
2714
|
+
|
|
2715
|
+
/**
|
|
2716
|
+
* Defines a column for use inside a `fu-table`.
|
|
2717
|
+
*
|
|
2718
|
+
* A `fu-column` describes how a single field of the row data is displayed,
|
|
2719
|
+
* sorted, edited, and configured.
|
|
2720
|
+
*
|
|
2721
|
+
* Columns can optionally provide custom templates for:
|
|
2722
|
+
* - header content
|
|
2723
|
+
* - header addon content
|
|
2724
|
+
* - data cell rendering
|
|
2725
|
+
* - row actions (edit, save, expand, etc.)
|
|
2726
|
+
*
|
|
2727
|
+
* Example:
|
|
2728
|
+
* ```html
|
|
2729
|
+
* <fu-table [data]="users">
|
|
2730
|
+
* <fu-column field="name" header="Name" sortable editable />
|
|
2731
|
+
*
|
|
2732
|
+
* <fu-column field="age" header="Age">
|
|
2733
|
+
* <ng-template fuColumnCell let-value>
|
|
2734
|
+
* {{ value }} years
|
|
2735
|
+
* </ng-template>
|
|
2736
|
+
* </fu-column>
|
|
2737
|
+
* </fu-table>
|
|
2738
|
+
* ```
|
|
2739
|
+
*
|
|
2740
|
+
* ## Visibility
|
|
2741
|
+
* The column visibility can be controlled in two ways:
|
|
2742
|
+
* - via the `visible` input on the column
|
|
2743
|
+
* - via `ColumnConfig` provided to `fu-table`
|
|
2744
|
+
*
|
|
2745
|
+
* If `ColumnConfig` is provided, it takes precedence over the column's
|
|
2746
|
+
* `visible` input.
|
|
2747
|
+
*/
|
|
2748
|
+
class FuColumnComponent {
|
|
2749
|
+
constructor(table) {
|
|
2750
|
+
/** Text displayed in the column header. */
|
|
2751
|
+
this.header = input('');
|
|
2752
|
+
/**
|
|
2753
|
+
* Path to the value in the row object used by this column.
|
|
2754
|
+
*
|
|
2755
|
+
* Supports dot-notation for nested values (e.g. `address.city`).
|
|
2756
|
+
*/
|
|
2757
|
+
this.field = input('');
|
|
2758
|
+
/**
|
|
2759
|
+
* Control type used when the column is editable.
|
|
2760
|
+
*
|
|
2761
|
+
* If not provided, the control type is inferred from the cell value
|
|
2762
|
+
* using `typeof` (e.g. `string`, `number`, `boolean`).
|
|
2763
|
+
*
|
|
2764
|
+
* ⚠️ Note: Date values cannot be reliably inferred from the value.
|
|
2765
|
+
*
|
|
2766
|
+
* ⚠️ When editing date fields, it is recommended to explicitly set `type="date"`.
|
|
2767
|
+
*
|
|
2768
|
+
* The control type determines which editor is rendered for the cell.
|
|
2769
|
+
*/
|
|
2770
|
+
this.type = input();
|
|
2771
|
+
/**
|
|
2772
|
+
* CSS width of the column (e.g. `120px`, `20%`, `auto`).
|
|
2773
|
+
* Defaults to `auto`.
|
|
2774
|
+
*/
|
|
2775
|
+
this.width = input('auto');
|
|
2776
|
+
/**
|
|
2777
|
+
* Whether the column can be sorted.
|
|
2778
|
+
*
|
|
2779
|
+
* When enabled, clicking the column header toggles sort direction.
|
|
2780
|
+
*/
|
|
2781
|
+
this.sortable = input(false, {
|
|
2782
|
+
transform: booleanAttribute,
|
|
2783
|
+
});
|
|
2784
|
+
/**
|
|
2785
|
+
* Optional backend sort key.
|
|
2786
|
+
*
|
|
2787
|
+
* When provided, this value is emitted in sort queries instead of `field`.
|
|
2788
|
+
* Useful when the backend expects enum values, aliases, or different field names.
|
|
2789
|
+
*
|
|
2790
|
+
* If omitted, `field` is used.
|
|
2791
|
+
*/
|
|
2792
|
+
this.sortField = input(null);
|
|
2793
|
+
/**
|
|
2794
|
+
* Whether cells in this column can be edited.
|
|
2795
|
+
*
|
|
2796
|
+
* Editing behavior depends on the configured control type and templates.
|
|
2797
|
+
*/
|
|
2798
|
+
this.editable = input(false, {
|
|
2799
|
+
transform: booleanAttribute,
|
|
2800
|
+
});
|
|
2801
|
+
/**
|
|
2802
|
+
* Whether the column can be hidden via table configuration UI
|
|
2803
|
+
* (e.g. sidebar or column visibility controls).
|
|
2804
|
+
*/
|
|
2805
|
+
this.hideable = input(true);
|
|
2806
|
+
/**
|
|
2807
|
+
* Whether the column can be reordered via drag-and-drop
|
|
2808
|
+
* or other column reordering mechanisms.
|
|
2809
|
+
*/
|
|
2810
|
+
this.reorderable = input(true);
|
|
2811
|
+
/**
|
|
2812
|
+
* Controls whether the column is initially visible.
|
|
2813
|
+
*
|
|
2814
|
+
* Note:
|
|
2815
|
+
* If `ColumnConfig` is provided to `fu-table`, the visibility defined
|
|
2816
|
+
* there takes precedence over this value.
|
|
2817
|
+
*/
|
|
2818
|
+
this.visible = model(true);
|
|
2819
|
+
this.sortDirection = signal(null);
|
|
2820
|
+
this.columnCellTemplate = contentChild(FuColumnCellTemplateDirective, {
|
|
2821
|
+
read: TemplateRef,
|
|
2822
|
+
});
|
|
2823
|
+
this.columnHeaderAddonTemplate = contentChild(FuColumnHeaderAddonTemplateDirective, {
|
|
2824
|
+
read: TemplateRef,
|
|
2825
|
+
});
|
|
2826
|
+
this.columnHeaderTemplate = contentChild(FuColumnHeaderTemplateDirective, {
|
|
2827
|
+
read: TemplateRef,
|
|
2828
|
+
});
|
|
2829
|
+
this.columnActionsTemplate = contentChild(FuColumnActionsTemplateDirective, {
|
|
2830
|
+
read: TemplateRef,
|
|
2831
|
+
});
|
|
2832
|
+
table.registerColumn(this);
|
|
2833
|
+
}
|
|
2834
|
+
toggleSort() {
|
|
2835
|
+
const current = this.sortDirection();
|
|
2836
|
+
this.sortDirection.set(current === 'asc' ? 'desc' : current === 'desc' ? null : 'asc');
|
|
2837
|
+
}
|
|
2838
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.6", ngImport: i0, type: FuColumnComponent, deps: [{ token: FuTableComponent }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
2839
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "19.0.6", type: FuColumnComponent, isStandalone: true, selector: "fu-column", inputs: { header: { classPropertyName: "header", publicName: "header", isSignal: true, isRequired: false, transformFunction: null }, field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: false, transformFunction: null }, type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, sortable: { classPropertyName: "sortable", publicName: "sortable", isSignal: true, isRequired: false, transformFunction: null }, sortField: { classPropertyName: "sortField", publicName: "sortField", isSignal: true, isRequired: false, transformFunction: null }, editable: { classPropertyName: "editable", publicName: "editable", isSignal: true, isRequired: false, transformFunction: null }, hideable: { classPropertyName: "hideable", publicName: "hideable", isSignal: true, isRequired: false, transformFunction: null }, reorderable: { classPropertyName: "reorderable", publicName: "reorderable", isSignal: true, isRequired: false, transformFunction: null }, visible: { classPropertyName: "visible", publicName: "visible", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { visible: "visibleChange" }, queries: [{ propertyName: "columnCellTemplate", first: true, predicate: FuColumnCellTemplateDirective, descendants: true, read: TemplateRef, isSignal: true }, { propertyName: "columnHeaderAddonTemplate", first: true, predicate: FuColumnHeaderAddonTemplateDirective, descendants: true, read: TemplateRef, isSignal: true }, { propertyName: "columnHeaderTemplate", first: true, predicate: FuColumnHeaderTemplateDirective, descendants: true, read: TemplateRef, isSignal: true }, { propertyName: "columnActionsTemplate", first: true, predicate: FuColumnActionsTemplateDirective, descendants: true, read: TemplateRef, isSignal: true }], ngImport: i0, template: "\r\n", styles: [""] }); }
|
|
2840
|
+
}
|
|
2841
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.6", ngImport: i0, type: FuColumnComponent, decorators: [{
|
|
2842
|
+
type: Component,
|
|
2843
|
+
args: [{ selector: 'fu-column', imports: [], template: "\r\n" }]
|
|
2844
|
+
}], ctorParameters: () => [{ type: FuTableComponent }] });
|
|
2845
|
+
|
|
2846
|
+
class FuTableModule {
|
|
2847
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.6", ngImport: i0, type: FuTableModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
|
|
2848
|
+
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.0.6", ngImport: i0, type: FuTableModule, imports: [FuTableComponent,
|
|
2849
|
+
FuColumnComponent,
|
|
2850
|
+
FuColumnCellTemplateDirective,
|
|
2851
|
+
FuColumnActionsTemplateDirective,
|
|
2852
|
+
FuColumnHeaderTemplateDirective,
|
|
2853
|
+
FuColumnHeaderAddonTemplateDirective,
|
|
2854
|
+
FuRowContextMenuTemplateDirective,
|
|
2855
|
+
FuTableExpandRowTemplateDirective,
|
|
2856
|
+
FuTableNoDataTemplateDirective,
|
|
2857
|
+
FuTableSidebarTemplateDirective,
|
|
2858
|
+
FuHighlightPipe,
|
|
2859
|
+
FuButtonDirective], exports: [FuTableComponent,
|
|
2860
|
+
FuColumnComponent,
|
|
2861
|
+
FuColumnCellTemplateDirective,
|
|
2862
|
+
FuColumnActionsTemplateDirective,
|
|
2863
|
+
FuColumnHeaderTemplateDirective,
|
|
2864
|
+
FuColumnHeaderAddonTemplateDirective,
|
|
2865
|
+
FuRowContextMenuTemplateDirective,
|
|
2866
|
+
FuTableExpandRowTemplateDirective,
|
|
2867
|
+
FuTableNoDataTemplateDirective,
|
|
2868
|
+
FuTableSidebarTemplateDirective,
|
|
2869
|
+
FuHighlightPipe,
|
|
2870
|
+
FuButtonDirective] }); }
|
|
2871
|
+
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.0.6", ngImport: i0, type: FuTableModule, imports: [FuTableComponent] }); }
|
|
2872
|
+
}
|
|
2873
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.6", ngImport: i0, type: FuTableModule, decorators: [{
|
|
2874
|
+
type: NgModule,
|
|
2875
|
+
args: [{
|
|
2876
|
+
declarations: [],
|
|
2877
|
+
imports: [
|
|
2878
|
+
FuTableComponent,
|
|
2879
|
+
FuColumnComponent,
|
|
2880
|
+
FuColumnCellTemplateDirective,
|
|
2881
|
+
FuColumnActionsTemplateDirective,
|
|
2882
|
+
FuColumnHeaderTemplateDirective,
|
|
2883
|
+
FuColumnHeaderAddonTemplateDirective,
|
|
2884
|
+
FuRowContextMenuTemplateDirective,
|
|
2885
|
+
FuTableExpandRowTemplateDirective,
|
|
2886
|
+
FuTableNoDataTemplateDirective,
|
|
2887
|
+
FuTableSidebarTemplateDirective,
|
|
2888
|
+
FuHighlightPipe,
|
|
2889
|
+
FuButtonDirective,
|
|
2890
|
+
],
|
|
2891
|
+
exports: [
|
|
2892
|
+
FuTableComponent,
|
|
2893
|
+
FuColumnComponent,
|
|
2894
|
+
FuColumnCellTemplateDirective,
|
|
2895
|
+
FuColumnActionsTemplateDirective,
|
|
2896
|
+
FuColumnHeaderTemplateDirective,
|
|
2897
|
+
FuColumnHeaderAddonTemplateDirective,
|
|
2898
|
+
FuRowContextMenuTemplateDirective,
|
|
2899
|
+
FuTableExpandRowTemplateDirective,
|
|
2900
|
+
FuTableNoDataTemplateDirective,
|
|
2901
|
+
FuTableSidebarTemplateDirective,
|
|
2902
|
+
FuHighlightPipe,
|
|
2903
|
+
FuButtonDirective,
|
|
2904
|
+
],
|
|
2905
|
+
}]
|
|
2906
|
+
}] });
|
|
2907
|
+
|
|
2908
|
+
/**
|
|
2909
|
+
* Creates a row class resolver that maps a row's field value to a CSS class name.
|
|
2910
|
+
*
|
|
2911
|
+
* Use this helper to easily produce a function you can pass to the table's
|
|
2912
|
+
* `rowClass` input. The returned resolver reads the specified `field` from the
|
|
2913
|
+
* row object, stringifies it, looks it up in `map` and returns the matching
|
|
2914
|
+
* class name or the provided `fallback`.
|
|
2915
|
+
*
|
|
2916
|
+
* Notes:
|
|
2917
|
+
* - The function performs a direct property access: `(row as any)[field]`.
|
|
2918
|
+
* - Map keys are matched against the stringified field value (numbers/booleans
|
|
2919
|
+
* are converted to strings).
|
|
2920
|
+
* - If the field is nested (e.g. `user.name`) use `rowClassByPath` instead.
|
|
2921
|
+
* - CSS classes returned by the resolver must be defined in the consumer
|
|
2922
|
+
* application's styles — the library does not inject those rules.
|
|
2923
|
+
*
|
|
2924
|
+
* Example:
|
|
2925
|
+
* ```ts
|
|
2926
|
+
* // rows: Array<{ sex: 'male'|'female' }>
|
|
2927
|
+
* rowClass = rowClassByField('sex', {
|
|
2928
|
+
* female: 'row-female',
|
|
2929
|
+
* male: 'row-male'
|
|
2930
|
+
* }, '');
|
|
2931
|
+
* ```
|
|
2932
|
+
*
|
|
2933
|
+
* @param field Property name on the row objects to check
|
|
2934
|
+
* @param map Mapping from stringified field values to CSS class names
|
|
2935
|
+
* @param fallback Optional fallback class when no mapping matches (default: '')
|
|
2936
|
+
* @returns A function `(row: T) => string` that resolves the class name
|
|
2937
|
+
*/
|
|
2938
|
+
function rowClassByField(field, map, fallback = '') {
|
|
2939
|
+
return (row) => {
|
|
2940
|
+
const value = String(row[field]);
|
|
2941
|
+
return map[value] ?? fallback;
|
|
2942
|
+
};
|
|
2943
|
+
}
|
|
2944
|
+
/**
|
|
2945
|
+
* Creates a row class resolver that reads a nested field (dot-path) from a row
|
|
2946
|
+
* object and maps its stringified value to a CSS class name.
|
|
2947
|
+
*
|
|
2948
|
+
* This helper is useful when the value you want to base row styling on lives
|
|
2949
|
+
* on a nested property (for example `user.status` or `address.country.code`).
|
|
2950
|
+
* The `path` supports simple dot-notation and is resolved with a safe reduce
|
|
2951
|
+
* (it will tolerate missing intermediate objects).
|
|
2952
|
+
*
|
|
2953
|
+
* Behavior notes:
|
|
2954
|
+
* - The resolved field value is converted to a string and matched against the
|
|
2955
|
+
* provided `map` keys.
|
|
2956
|
+
* - If no mapping matches, the `fallback` value is returned (defaults to `''`).
|
|
2957
|
+
* - The returned CSS class must be defined by the consumer application — the
|
|
2958
|
+
* library does not provide styling for custom class names.
|
|
2959
|
+
*
|
|
2960
|
+
* Example (rows where `sex` is a nested property):
|
|
2961
|
+
* ```ts
|
|
2962
|
+
* // rows: Array<{ user: { sex: 'male'|'female' } }>
|
|
2963
|
+
* rowClass = rowClassByPath('user.sex', {
|
|
2964
|
+
* female: 'row-female',
|
|
2965
|
+
* male: 'row-male'
|
|
2966
|
+
* }, '');
|
|
2967
|
+
* ```
|
|
2968
|
+
*
|
|
2969
|
+
* @param path Dot-separated path to the property on the row objects
|
|
2970
|
+
* @param map Mapping from stringified values to CSS class names
|
|
2971
|
+
* @param fallback Optional fallback class when no mapping matches (default: '')
|
|
2972
|
+
* @returns A function `(row: T) => string` that resolves the class name
|
|
2973
|
+
*/
|
|
2974
|
+
function rowClassByPath(path, map, fallback = '') {
|
|
2975
|
+
return (row) => {
|
|
2976
|
+
const value = path.split('.').reduce((acc, key) => acc?.[key], row);
|
|
2977
|
+
return map[String(value)] ?? fallback;
|
|
2978
|
+
};
|
|
2979
|
+
}
|
|
2980
|
+
/**
|
|
2981
|
+
* Creates a strongly typed field path factory for a specific row type.
|
|
2982
|
+
*
|
|
2983
|
+
* This helper is used to create type-safe string paths that reference
|
|
2984
|
+
* properties of a data model (including nested properties), while
|
|
2985
|
+
* keeping the runtime value a simple string.
|
|
2986
|
+
*
|
|
2987
|
+
* It improves developer experience by:
|
|
2988
|
+
* - validating field paths at compile time
|
|
2989
|
+
* - preventing typos in nested property paths
|
|
2990
|
+
* - keeping IDE autocompletion and hover information clean
|
|
2991
|
+
*
|
|
2992
|
+
* ## Usage
|
|
2993
|
+
* ```ts
|
|
2994
|
+
* const fieldOf = fieldPathOf<Person>();
|
|
2995
|
+
*
|
|
2996
|
+
* const name = fieldOf('name'); // ✅ valid
|
|
2997
|
+
* const city = fieldOf('address.city'); // ✅ valid
|
|
2998
|
+
* const wrong = fieldOf('address.cito'); // ❌ compile-time error
|
|
2999
|
+
* ```
|
|
3000
|
+
*
|
|
3001
|
+
* The returned value is a string at runtime and can be safely used for:
|
|
3002
|
+
* - column definitions
|
|
3003
|
+
* - sorting and filtering
|
|
3004
|
+
* - backend queries
|
|
3005
|
+
*
|
|
3006
|
+
* @typeParam T The row data type used to validate field paths.
|
|
3007
|
+
*/
|
|
3008
|
+
function fieldPathOf() {
|
|
3009
|
+
return (path) => path;
|
|
3010
|
+
}
|
|
3011
|
+
|
|
1383
3012
|
/*
|
|
1384
3013
|
* Public API Surface of ng-fusion-ui
|
|
1385
3014
|
*/
|
|
@@ -1388,5 +3017,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.6", ngImpor
|
|
|
1388
3017
|
* Generated bundle index. Do not edit.
|
|
1389
3018
|
*/
|
|
1390
3019
|
|
|
1391
|
-
export { BodyTemplateDirective, ButtonDirective, ButtonModule, DataTableModule, ExpandTemplateDirective, FilledButtonDirective, HeaderTemplateDirective, HighlightDirective, IconComponent, IconModule, OutlinedButtonDirective, PopupTemplateDirective, TableComponent, TableFilterComponent, TableIntlService, TablePaginatorComponent, TbodyActionsComponent, TbodyCellComponent, TextButtonDirective, TheadCellComponent, TreeComponent, TreeModule };
|
|
3020
|
+
export { BodyTemplateDirective, ButtonDirective, ButtonModule, DataTableModule, ExpandTemplateDirective, FilledButtonDirective, FuButtonDirective, FuColumnActionsTemplateDirective, FuColumnCellTemplateDirective, FuColumnComponent, FuColumnHeaderAddonTemplateDirective, FuColumnHeaderTemplateDirective, FuHighlightPipe, FuRowContextMenuTemplateDirective, FuTableComponent, FuTableExpandRowTemplateDirective, FuTableIntlService, FuTableModule, FuTableNoDataTemplateDirective, FuTableSidebarTemplateDirective, HeaderTemplateDirective, HighlightDirective, IconComponent, IconModule, OutlinedButtonDirective, PopupTemplateDirective, TableComponent, TableFilterComponent, TableIntlService, TablePaginatorComponent, TbodyActionsComponent, TbodyCellComponent, TextButtonDirective, TheadCellComponent, TreeComponent, TreeModule, fieldPathOf, rowClassByField, rowClassByPath };
|
|
1392
3021
|
//# sourceMappingURL=ng-fusion-ui.mjs.map
|