ng-prime-tools
Version:
An advanced PrimeNG table for Angular
1 lines • 705 kB
Source Map (JSON)
{"version":3,"file":"ng-prime-tools.mjs","sources":["../../../projects/ng-prime-tools/src/lib/utils/text.util.ts","../../../projects/ng-prime-tools/src/lib/pipes/custom-currency.ts","../../../projects/ng-prime-tools/src/lib/pipes/custom-date.pipe.ts","../../../projects/ng-prime-tools/src/lib/pt-advanced-prime-table/pt-advanced-prime-table.component.ts","../../../projects/ng-prime-tools/src/lib/pt-advanced-prime-table/pt-advanced-prime-table.component.html","../../../projects/ng-prime-tools/src/lib/pt-multi-select/pt-multi-select.component.ts","../../../projects/ng-prime-tools/src/lib/pt-multi-select/pt-multi-select.component.html","../../../projects/ng-prime-tools/src/lib/pt-multi-select/pt-multi-select.module.ts","../../../projects/ng-prime-tools/src/lib/pt-button/pt-button.component.ts","../../../projects/ng-prime-tools/src/lib/pt-button/pt-button.component.html","../../../projects/ng-prime-tools/src/lib/pt-button/pt-button.module.ts","../../../projects/ng-prime-tools/src/lib/pt-advanced-prime-table/pt-advanced-prime-table.module.ts","../../../projects/ng-prime-tools/src/lib/pt-advanced-prime-table/date-utility.service.ts","../../../projects/ng-prime-tools/src/lib/enums/table-type.enum.ts","../../../projects/ng-prime-tools/src/lib/enums/search-criteria-type.enum.ts","../../../projects/ng-prime-tools/src/lib/enums/form-input-type.enum.ts","../../../projects/ng-prime-tools/src/lib/enums/button-color.enum.ts","../../../projects/ng-prime-tools/src/lib/enums/input-validation.enum.ts","../../../projects/ng-prime-tools/src/lib/enums/badge-type.enum.ts","../../../projects/ng-prime-tools/src/lib/enums/severity.enum.ts","../../../projects/ng-prime-tools/src/lib/enums/align.enum.ts","../../../projects/ng-prime-tools/src/lib/enums/public-api.ts","../../../projects/ng-prime-tools/src/lib/multi-search-criteria/multi-search-criteria.component.ts","../../../projects/ng-prime-tools/src/lib/multi-search-criteria/multi-search-criteria.component.html","../../../projects/ng-prime-tools/src/lib/multi-search-criteria/multi-search-criteria.module.ts","../../../projects/ng-prime-tools/src/lib/pt-check-box-input/pt-check-box-input.component.ts","../../../projects/ng-prime-tools/src/lib/pt-check-box-input/pt-check-box-input.component.html","../../../projects/ng-prime-tools/src/lib/utils/date.util.ts","../../../projects/ng-prime-tools/src/lib/pt-date-input/pt-date.service.ts","../../../projects/ng-prime-tools/src/lib/pt-date-input/pt-date-input.component.ts","../../../projects/ng-prime-tools/src/lib/pt-date-input/pt-date-input.component.html","../../../projects/ng-prime-tools/src/lib/pt-number-input/pt-number-input.component.ts","../../../projects/ng-prime-tools/src/lib/pt-number-input/pt-number-input.component.html","../../../projects/ng-prime-tools/src/lib/pt-switch-input/pt-switch-input.component.ts","../../../projects/ng-prime-tools/src/lib/pt-switch-input/pt-switch-input.component.html","../../../projects/ng-prime-tools/src/lib/pt-text-area-input/pt-text-area-input.component.ts","../../../projects/ng-prime-tools/src/lib/pt-text-area-input/pt-text-area-input.component.html","../../../projects/ng-prime-tools/src/lib/pt-text-input/pt-text-input.component.ts","../../../projects/ng-prime-tools/src/lib/pt-text-input/pt-text-input.component.html","../../../projects/ng-prime-tools/src/lib/pt-dropdown/pt-dropdown.component.ts","../../../projects/ng-prime-tools/src/lib/pt-dropdown/pt-dropdown.component.html","../../../projects/ng-prime-tools/src/lib/pt-otp-input/pt-otp-input.component.ts","../../../projects/ng-prime-tools/src/lib/pt-otp-input/pt-otp-input.component.html","../../../projects/ng-prime-tools/src/lib/pt-password-input/pt-password-input.component.ts","../../../projects/ng-prime-tools/src/lib/pt-password-input/pt-password-input.component.html","../../../projects/ng-prime-tools/src/lib/pt-form-builder/pt-dynamic-form-field/pt-dynamic-form-field.component.ts","../../../projects/ng-prime-tools/src/lib/pt-form-builder/pt-dynamic-form-field/pt-dynamic-form-field.component.html","../../../projects/ng-prime-tools/src/lib/pt-form-builder/pt-form-builder.component.ts","../../../projects/ng-prime-tools/src/lib/pt-form-builder/pt-form-builder.component.html","../../../projects/ng-prime-tools/src/lib/pt-check-box-input/pt-check-box-input.module.ts","../../../projects/ng-prime-tools/src/lib/pt-number-input/pt-number-input.module.ts","../../../projects/ng-prime-tools/src/lib/pt-switch-input/pt-switch-input.module.ts","../../../projects/ng-prime-tools/src/lib/pt-text-input/pt-text-input.module.ts","../../../projects/ng-prime-tools/src/lib/pt-text-area-input/pt-text-area-input.module.ts","../../../projects/ng-prime-tools/src/lib/pt-date-input/pt-date-input.module.ts","../../../projects/ng-prime-tools/src/lib/pt-dropdown/pt-dropdown.module.ts","../../../projects/ng-prime-tools/src/lib/pt-otp-input/pt-otp-input.module.ts","../../../projects/ng-prime-tools/src/lib/pt-password-input/pt-password-input.module.ts","../../../projects/ng-prime-tools/src/lib/pt-form-builder/pt-dynamic-form-field/pt-dynamic-form-field-module.ts","../../../projects/ng-prime-tools/src/lib/pt-form-builder/pt-form-builder.module.ts","../../../projects/ng-prime-tools/src/lib/pt-metric-card/pt-metric-card.component.ts","../../../projects/ng-prime-tools/src/lib/pt-metric-card/pt-metric-card.component.html","../../../projects/ng-prime-tools/src/lib/pt-metric-card/pt-metric-card.module.ts","../../../projects/ng-prime-tools/src/lib/pt-metric-card-group/pt-metric-card-group.component.ts","../../../projects/ng-prime-tools/src/lib/pt-metric-card-group/pt-metric-card-group.component.html","../../../projects/ng-prime-tools/src/lib/pt-metric-card-group/pt-metric-card-group.module.ts","../../../projects/ng-prime-tools/src/lib/pt-chart/pt-chart.component.ts","../../../projects/ng-prime-tools/src/lib/pt-chart/pt-chart.component.html","../../../projects/ng-prime-tools/src/lib/pt-chart/pt-chart.module.ts","../../../projects/ng-prime-tools/src/lib/pt-menu/pt-menu.component.ts","../../../projects/ng-prime-tools/src/lib/pt-menu/pt-menu.component.html","../../../projects/ng-prime-tools/src/lib/pt-card/pt-card.component.ts","../../../projects/ng-prime-tools/src/lib/pt-card/pt-card.component.html","../../../projects/ng-prime-tools/src/lib/pt-menu/pt-menu.module.ts","../../../projects/ng-prime-tools/src/lib/pt-card/pt-card.module.ts","../../../projects/ng-prime-tools/src/lib/pt-menu-fancy/pt-menu-fancy.component.ts","../../../projects/ng-prime-tools/src/lib/pt-menu-fancy/pt-menu-fancy.component.html","../../../projects/ng-prime-tools/src/lib/pt-nav-bar-menu/pt-nav-bar-menu.component.ts","../../../projects/ng-prime-tools/src/lib/pt-nav-bar-menu/pt-nav-bar-menu.component.html","../../../projects/ng-prime-tools/src/lib/pt-menu-fancy/pt-menu-fancy.module.ts","../../../projects/ng-prime-tools/src/lib/pt-nav-bar-menu/pt-nav-bar-menu.module.ts","../../../projects/ng-prime-tools/src/lib/pt-side-bar-menu/pt-side-bar-menu.component.ts","../../../projects/ng-prime-tools/src/lib/pt-side-bar-menu/pt-side-bar-menu.component.html","../../../projects/ng-prime-tools/src/lib/pt-side-bar-menu/pt-side-bar-menu.module.ts","../../../projects/ng-prime-tools/src/lib/pt-footer/pt-footer.component.ts","../../../projects/ng-prime-tools/src/lib/pt-footer/pt-footer.component.html","../../../projects/ng-prime-tools/src/lib/pt-bread-crumb/pt-bread-crumb.service.ts","../../../projects/ng-prime-tools/src/lib/pt-bread-crumb/pt-bread-crumb.component.ts","../../../projects/ng-prime-tools/src/lib/pt-bread-crumb/pt-bread-crumb.component.html","../../../projects/ng-prime-tools/src/lib/pt-page-skeleton/pt-page-skeleton.component.ts","../../../projects/ng-prime-tools/src/lib/pt-page-skeleton/pt-page-skeleton.component.html","../../../projects/ng-prime-tools/src/lib/pt-footer/pt-footer.module.ts","../../../projects/ng-prime-tools/src/lib/pt-bread-crumb/pt-bread-crumb.module.ts","../../../projects/ng-prime-tools/src/lib/pt-page-skeleton/pt-page-skeleton.module.ts","../../../projects/ng-prime-tools/src/lib/pt-login-page/pt-login-card/pt-login-card.component.ts","../../../projects/ng-prime-tools/src/lib/pt-login-page/pt-login-card/pt-login-card.component.html","../../../projects/ng-prime-tools/src/lib/pt-login-page/pt-login-page.component.ts","../../../projects/ng-prime-tools/src/lib/pt-login-page/pt-login-page.component.html","../../../projects/ng-prime-tools/src/lib/pt-login-page/pt-login-page.module.ts","../../../projects/ng-prime-tools/src/lib/pt-confirm-dialog/pt-confirm-dialog.config.ts","../../../projects/ng-prime-tools/src/lib/pt-confirm-dialog/pt-confirm-dialog.component.ts","../../../projects/ng-prime-tools/src/lib/pt-confirm-dialog/pt-confirm-dialog.component.html","../../../projects/ng-prime-tools/src/lib/pt-confirm-dialog/pt-confirm-dialog.module.ts","../../../projects/ng-prime-tools/src/lib/pt-toast-notifier/pt-toast-notifier.component.ts","../../../projects/ng-prime-tools/src/lib/pt-toast-notifier/pt-toast-notifier.component.html","../../../projects/ng-prime-tools/src/lib/pt-toast-notifier/pt-toast-notifier.module.ts","../../../projects/ng-prime-tools/src/lib/pt-dialog/pt-dialog.component.ts","../../../projects/ng-prime-tools/src/lib/pt-dialog/pt-dialog.component.html","../../../projects/ng-prime-tools/src/lib/pt-dialog/pt-dialog.module.ts","../../../projects/ng-prime-tools/src/lib/pt-otp-page/pt-otp-card/pt-otp-card.component.ts","../../../projects/ng-prime-tools/src/lib/pt-otp-page/pt-otp-card/pt-otp-card.component.html","../../../projects/ng-prime-tools/src/lib/pt-otp-page/pt-otp-page.component.ts","../../../projects/ng-prime-tools/src/lib/pt-otp-page/pt-otp-page.component.html","../../../projects/ng-prime-tools/src/lib/pt-otp-page/pt-otp-page.module.ts","../../../projects/ng-prime-tools/src/lib/ng-prime-tools.module.ts","../../../projects/ng-prime-tools/src/lib/pt-metric-panel/pt-metric-panel.component.ts","../../../projects/ng-prime-tools/src/lib/pt-metric-panel/pt-metric-panel.component.html","../../../projects/ng-prime-tools/src/lib/pt-metric-panel/pt-metric-panel.module.ts","../../../projects/ng-prime-tools/src/lib/pt-chart-comparison/pt-chart-comparison.component.ts","../../../projects/ng-prime-tools/src/lib/pt-chart-comparison/pt-chart-comparison.component.html","../../../projects/ng-prime-tools/src/lib/pt-chart-comparison/pt-chart-comparison.module.ts","../../../projects/ng-prime-tools/src/lib/pt-line-chart/pt-line-chart.component.ts","../../../projects/ng-prime-tools/src/lib/pt-line-chart/pt-line-chart.component.html","../../../projects/ng-prime-tools/src/lib/pt-line-chart/pt-line-chart.module.ts","../../../projects/ng-prime-tools/src/lib/pt-change-password-page/pt-change-password-card/pt-change-password-card.config.ts","../../../projects/ng-prime-tools/src/lib/pt-change-password-page/pt-change-password-card/pt-change-password-card.component.ts","../../../projects/ng-prime-tools/src/lib/pt-change-password-page/pt-change-password-card/pt-change-password-card.component.html","../../../projects/ng-prime-tools/src/lib/pt-change-password-page/pt-change-password-page.component.ts","../../../projects/ng-prime-tools/src/lib/pt-change-password-page/pt-change-password-page.component.html","../../../projects/ng-prime-tools/src/lib/pt-change-password-page/pt-change-password-page.module.ts","../../../projects/ng-prime-tools/src/lib/models/nav-bar-menu-config.model.ts","../../../projects/ng-prime-tools/src/lib/models/pt-dialog-config.model.ts","../../../projects/ng-prime-tools/src/lib/models/public-api.ts","../../../projects/ng-prime-tools/src/lib/pt-group/pt-group.component.ts","../../../projects/ng-prime-tools/src/lib/pt-group/pt-group.component.html","../../../projects/ng-prime-tools/src/lib/pt-group/pt-group.module.ts","../../../projects/ng-prime-tools/src/ng-prime-tools.ts"],"sourcesContent":["import { TableColumn } from '../models';\n\n/**\n * Calculates the width required for a column based on the header text (column title).\n * It uses the Canvas API to measure text width dynamically.\n *\n * @param {TableColumn} col - The column metadata containing the header title and code.\n * @param {string} [font='16px Arial'] - The font to use for measurement (defaults to '16px Arial').\n * @returns {number} - The calculated width of the column in pixels.\n */\nexport function calculateTextWidth(\n col: TableColumn,\n font: string = '16px Arial'\n): number {\n // Create a canvas context for measuring text\n const canvas = document.createElement('canvas');\n const context = canvas.getContext('2d');\n\n if (!context) {\n return 100; // Fallback width if canvas context is not available\n }\n\n // Set the font to match the provided font or the document body font\n context.font = font || getComputedStyle(document.body).font;\n\n // Measure the header text width\n const headerWidth = context.measureText(col.title).width;\n\n // Return the width with some padding\n return Math.ceil(headerWidth + 20); // Add padding for extra space\n}\n","import { Pipe, PipeTransform } from '@angular/core';\n\n@Pipe({\n name: 'customCurrency',\n standalone: true,\n})\nexport class CustomCurrencyPipe implements PipeTransform {\n transform(\n value: number,\n currency?: string,\n decimalPlaces?: number,\n thousandSeparator: 'comma' | 'space' = 'comma',\n decimalSeparator: 'comma' | 'dot' = 'dot'\n ): string {\n let formattedValue: string;\n\n if (decimalPlaces !== undefined) {\n formattedValue = value.toFixed(decimalPlaces);\n } else {\n formattedValue = value.toString();\n }\n\n const thousandSeparatorChar = thousandSeparator === 'space' ? ' ' : ',';\n const decimalSeparatorChar = decimalSeparator === 'comma' ? ',' : '.';\n\n formattedValue = formattedValue.replace(\n /\\B(?=(\\d{3})+(?!\\d))/g,\n thousandSeparatorChar\n );\n\n if (decimalSeparatorChar === ',') {\n formattedValue = formattedValue.replace('.', ',');\n }\n\n if (currency) {\n let formattedCurrency: string;\n\n switch (currency) {\n case 'MAD':\n formattedCurrency = `${formattedValue} DH`;\n break;\n case 'USD':\n formattedCurrency = `$${formattedValue}`;\n break;\n default:\n formattedCurrency = `${formattedValue} ${currency}`;\n }\n\n return formattedCurrency;\n }\n\n return formattedValue;\n }\n}\n","import { Pipe, PipeTransform } from '@angular/core';\n\n@Pipe({\n name: 'customDate',\n standalone: false\n})\nexport class CustomDatePipe implements PipeTransform {\n transform(value: any, format: string = 'dd/MM/yyyy'): string | null {\n if (!value) return null;\n\n if (typeof value === 'string') {\n const parts = value.split('/');\n if (parts.length === 3) {\n const day = parseInt(parts[0], 10);\n const month = parseInt(parts[1], 10) - 1;\n const year = parseInt(parts[2], 10);\n const date = new Date(year, month, day);\n\n if (isNaN(date.getTime())) return null;\n\n const options: Intl.DateTimeFormatOptions = {\n year: 'numeric',\n month: '2-digit',\n day: '2-digit',\n };\n return new Intl.DateTimeFormat('en-GB', options).format(date);\n }\n } else if (value instanceof Date) {\n // If the value is already a Date object\n const options: Intl.DateTimeFormatOptions = {\n year: 'numeric',\n month: '2-digit',\n day: '2-digit',\n };\n return new Intl.DateTimeFormat('en-GB', options).format(value);\n }\n\n return null;\n }\n}\n","import {\n Component,\n EventEmitter,\n Input,\n OnChanges,\n OnInit,\n Output,\n SimpleChanges,\n ViewChild,\n} from '@angular/core';\nimport { Table, TableRowReorderEvent } from 'primeng/table';\nimport {\n AlignEnum,\n ImageStyle,\n SeverityEnum,\n TableColumn,\n TableTypeEnum,\n TitleStyle,\n} from '../../public-api';\nimport { calculateTextWidth } from '../utils/text.util';\n\nexport interface PTTableActionConfig {\n code: string;\n icon?: string;\n styleClass?: string;\n tooltip?: string;\n action: any;\n visible?: boolean | ((row: any) => boolean);\n disabled?: boolean | ((row: any) => boolean);\n}\n\nexport interface PTTableLazyLoadEvent {\n page: number;\n rows: number;\n first: number;\n search?: string;\n sortField?: string | null;\n sortOrder?: 1 | -1 | null;\n filters?: Record<string, any>;\n}\n\nexport interface PTTableRowOrderItem {\n id: any;\n order: number;\n}\n\nexport interface PTTableRowReorderEvent {\n oldIndex: number;\n newIndex: number;\n movedRow: any;\n data: any[];\n ordering: PTTableRowOrderItem[];\n}\n\ntype DynamicStyleValue =\n | { [key: string]: string }\n | ((row: any) => { [key: string]: string });\n\ntype DynamicTagValue<T> = T | ((row: any) => T);\ntype DynamicProgressValue<T> = T | ((row: any) => T);\n\ntype ExtendedTableColumn = TableColumn & {\n cellStyle?: DynamicStyleValue;\n composedCellStyles?: Record<string, DynamicStyleValue>;\n tagSeverity?: DynamicTagValue<SeverityEnum>;\n tagValue?: DynamicTagValue<string>;\n tagIcon?: DynamicTagValue<string>;\n tagRounded?: boolean;\n\n progressValue?: DynamicProgressValue<number>;\n progressShowValue?: boolean;\n progressUnit?: string;\n progressSeverity?: DynamicProgressValue<SeverityEnum>;\n};\n\n@Component({\n selector: 'pt-advanced-prime-table',\n templateUrl: './pt-advanced-prime-table.component.html',\n styleUrl: './pt-advanced-prime-table.component.css',\n standalone: false,\n})\nexport class PTAdvancedPrimeTableComponent implements OnInit, OnChanges {\n @Input() data: any[] = [];\n @Input() columns: TableColumn[] = [];\n @Input() totalRecords = 0;\n @Input() rowsPerPage: number[] = [10, 20, 30];\n @Input() hasSearchFilter = false;\n @Input() hasExportExcel = false;\n @Input() hasExportPDF = false;\n @Input() hasColumnFilter = false;\n @Input() cellPadding = '0.5rem 0.75rem';\n @Input() isPaginated = true;\n @Input() isLazy = false;\n\n @Input() actions: PTTableActionConfig[] = [];\n @Input() isSortable = false;\n @Input() loading = false;\n @Input() maxHeight: string | null = null;\n\n @Input() isRowReorderable = false;\n @Input() rowReorderIdField: string = 'id';\n @Input() rowOrderStartAt = 1;\n\n @Input() selectionDataKey = 'id';\n @Input() selectionMode: 'single' | 'multiple' | null = null;\n @Input() selection: any | any[] | null = null;\n @Input() cellHeight: string | null = null;\n\n @Output() selectionChange = new EventEmitter<any>();\n @Output() rowSelect = new EventEmitter<any>();\n @Output() rowUnselect = new EventEmitter<any>();\n\n @Output() lazyLoad = new EventEmitter<PTTableLazyLoadEvent>();\n @Output() search = new EventEmitter<string>();\n @Output() exportExcelEvent = new EventEmitter<void>();\n @Output() exportPdfEvent = new EventEmitter<void>();\n @Output() onPageChange = new EventEmitter<{ page: number; rows: number }>();\n @Output() onSortColumn = new EventEmitter<any>();\n @Output() onFilterColumn = new EventEmitter<any>();\n @Output() rowReorderChange = new EventEmitter<PTTableRowReorderEvent>();\n @Output() filteredData = new EventEmitter<any[]>();\n\n @ViewChild('dt', { static: false }) dt!: Table;\n\n public TableTypeEnum = TableTypeEnum;\n public AlignEnum = AlignEnum;\n public SeverityEnum = SeverityEnum;\n\n searchValue = '';\n\n public filters: { [key: string]: any } = {};\n latestFilterValues: { [field: string]: any } = {};\n private clearedFields = new Set<string>();\n\n private validCurrencyCodes = ['USD', 'EUR', 'MAD'];\n iconWidth = 77;\n\n rows = 0;\n first = 0;\n currentPage = 0;\n currentSortField: string | null = null;\n currentSortOrder: 1 | -1 | null = null;\n\n hasGroupedColumns = false;\n\n isDelete = false;\n isEdit = false;\n\n Delete: (value: any) => void = () => {};\n initEditableRow: (data: any) => void = () => {};\n saveEditableRow: (data: any) => void = () => {};\n cancelEditableRow: (item: any) => void = () => {};\n\n customActions: PTTableActionConfig[] = [];\n\n dataMap = new Map<any, any>();\n map = new Map<any, Map<any, any>>();\n optionEntries = new Map<string, any[]>();\n optionValues: any[] = [];\n globalFilterFields: string[] = [];\n\n ngOnInit(): void {\n this.hasGroupedColumns = this.columns.some(\n (col) => col.children && col.children.length > 0,\n );\n\n this.globalFilterFields = this.columns\n .filter((col) => col.code !== undefined && col.isFilter !== false)\n .map((col) => col.code as string);\n\n this.initializePagination();\n this.initializeActions();\n\n this.columns.forEach((col) => {\n if (col.type === TableTypeEnum.ACTION) {\n col.isEditable = false;\n col.isFilter = false;\n col.isSortable = false;\n }\n\n if (\n col.type === TableTypeEnum.TAG ||\n col.type === TableTypeEnum.PROGRESS\n ) {\n col.isEditable = false;\n }\n\n if (col.type === TableTypeEnum.COMPOSED) {\n this.initializeComposedFilters(col);\n }\n\n if (col.isSortable === undefined) col.isSortable = true;\n if (col.isEditable === undefined) col.isEditable = true;\n\n if (col.isFilter !== false && col.code !== undefined) {\n if (!this.globalFilterFields.includes(col.code)) {\n this.globalFilterFields.push(col.code);\n }\n }\n\n if (!col.width) col.width = this.calculateColumnWidth(col);\n });\n\n if (!this.isLazy) {\n this.totalRecords = this.data?.length ?? 0;\n }\n }\n\n ngOnChanges(changes: SimpleChanges): void {\n if (changes['data'] && !this.isLazy) {\n this.totalRecords = this.data?.length ?? 0;\n\n if (this.dt) this.dt.first = 0;\n }\n }\n\n private emitLazyLoad(): void {\n const payload: PTTableLazyLoadEvent = {\n page: this.currentPage,\n rows: this.rows,\n first: this.first,\n search: this.searchValue?.trim() || undefined,\n sortField: this.currentSortField,\n sortOrder: this.currentSortOrder,\n filters: { ...this.latestFilterValues },\n };\n\n this.lazyLoad.emit(payload);\n }\n\n private resetToFirstPage(): void {\n this.currentPage = 0;\n this.first = 0;\n\n if (this.dt) {\n this.dt.first = 0;\n }\n }\n\n canUseRowReorder(): boolean {\n return this.isRowReorderable && !this.isLazy;\n }\n\n private buildRowOrdering(data: any[]): PTTableRowOrderItem[] {\n const idField: string = this.rowReorderIdField || 'id';\n\n return (data ?? []).map((row, index) => ({\n id: row?.[idField],\n order: this.rowOrderStartAt + index,\n }));\n }\n\n onRowReorder(event: TableRowReorderEvent): void {\n const oldIndex = event.dragIndex ?? -1;\n const newIndex = event.dropIndex ?? -1;\n\n if (oldIndex < 0 || newIndex < 0) {\n return;\n }\n\n const reorderedData = [...(this.data ?? [])];\n\n if (!reorderedData.length || newIndex >= reorderedData.length) {\n return;\n }\n\n const movedRow = reorderedData[newIndex] ?? null;\n\n this.data = reorderedData;\n\n if (!this.isLazy) {\n this.totalRecords = this.data.length;\n }\n\n this.rowReorderChange.emit({\n oldIndex,\n newIndex,\n movedRow,\n data: [...this.data],\n ordering: this.buildRowOrdering(this.data),\n });\n }\n\n getHeaderTitleClass(col: TableColumn): string {\n const align = col.headerAlign ?? AlignEnum.LEFT;\n\n switch (align) {\n case AlignEnum.CENTER:\n return 'header-title-center';\n case AlignEnum.RIGHT:\n return 'header-title-right';\n default:\n return 'header-title-left';\n }\n }\n\n getHeaderAlignClass(col: TableColumn): string {\n const align = col.headerAlign ?? AlignEnum.LEFT;\n\n switch (align) {\n case AlignEnum.CENTER:\n return 'header-align-center';\n case AlignEnum.RIGHT:\n return 'header-align-right';\n default:\n return 'header-align-left';\n }\n }\n\n getDataAlignClass(col: TableColumn): string {\n const effectiveAlign =\n col.dataAlign ??\n (col.type === TableTypeEnum.NUMBER || col.type === TableTypeEnum.AMOUNT\n ? AlignEnum.RIGHT\n : AlignEnum.LEFT);\n\n switch (effectiveAlign) {\n case AlignEnum.CENTER:\n return 'cell-align-center';\n case AlignEnum.RIGHT:\n return 'cell-align-right';\n default:\n return 'cell-align-left';\n }\n }\n\n getCellInnerAlignClass(col: TableColumn): string {\n const effectiveAlign =\n col.dataAlign ??\n (col.type === TableTypeEnum.NUMBER || col.type === TableTypeEnum.AMOUNT\n ? AlignEnum.RIGHT\n : AlignEnum.LEFT);\n\n switch (effectiveAlign) {\n case AlignEnum.CENTER:\n return 'cell-inner-center';\n case AlignEnum.RIGHT:\n return 'cell-inner-right';\n default:\n return 'cell-inner-left';\n }\n }\n\n private initializeActions(): void {\n this.isDelete = false;\n this.isEdit = false;\n this.Delete = () => {};\n this.initEditableRow = () => {};\n this.saveEditableRow = () => {};\n this.cancelEditableRow = () => {};\n this.customActions = [];\n\n if (!this.actions || this.actions.length === 0) return;\n\n this.actions.forEach((action) => {\n switch (action.code) {\n case 'delete':\n this.isDelete = true;\n this.Delete = (row: any) => action.action(row);\n break;\n case 'edit':\n this.initializeEditActions(action);\n break;\n default:\n this.customActions.push(action);\n break;\n }\n });\n }\n\n private initializeEditActions(action: PTTableActionConfig): void {\n this.isEdit = true;\n this.initEditableRow = (data: any) => action.action.init(data);\n this.saveEditableRow = (data: any) => {\n const record = this.map.get(data.id);\n action.action.save(data, record);\n this.dataMap.clear();\n };\n this.cancelEditableRow = (item: any) => console.log(item);\n }\n\n onCustomActionClick(action: PTTableActionConfig, row: any): void {\n if (!this.isActionVisible(action, row)) return;\n if (this.isActionDisabled(action, row)) return;\n\n if (action && typeof action.action === 'function') {\n setTimeout(() => action.action(row), 0);\n }\n }\n\n private initializeComposedFilters(col: TableColumn): void {\n col.composedNames?.forEach((composedName) => {\n const code = (col.code as string) || '';\n const composedCode = code + '.' + composedName;\n\n if (!this.globalFilterFields.includes(composedCode)) {\n this.globalFilterFields.push(composedCode);\n }\n\n this.filters[composedName] = {\n options: col.filterOptions,\n value: [],\n label: 'Filter by ' + composedName,\n placeholder: 'Select option',\n };\n });\n }\n\n getComposedFieldType(\n col: TableColumn,\n composedName: string,\n ): TableTypeEnum | undefined {\n if (col.composedNames && col.composedTypes) {\n const index = col.composedNames.indexOf(composedName);\n if (index >= 0 && index < col.composedTypes.length) {\n return col.composedTypes[index];\n }\n }\n\n return undefined;\n }\n\n onComposedColumnClear(col: TableColumn): void {\n if (!col.code || !col.composedNames) return;\n\n col.composedNames.forEach((name) => {\n const key = `${col.code}.${name}`;\n delete this.latestFilterValues[key];\n\n if (this.filters[name]) {\n this.filters[name].value = [];\n }\n\n this.clearedFields.add(key);\n });\n }\n\n onComposedFilterValueChange(\n col: TableColumn,\n composedName: string,\n value: any[],\n filterModel?: any,\n ): void {\n const key = `${col.code}.${composedName}`;\n\n if (filterModel) {\n filterModel.value = value;\n\n if (\n Array.isArray(filterModel.constraints) &&\n filterModel.constraints.length > 0\n ) {\n filterModel.constraints[0].value = value;\n }\n }\n\n if (!this.filters[composedName]) {\n this.filters[composedName] = {\n options: col.filterOptions,\n value: [],\n placeholder: 'Select option',\n };\n }\n\n this.filters[composedName].value = value || [];\n\n const isEmpty = !value || (Array.isArray(value) && value.length === 0);\n\n if (isEmpty) {\n delete this.latestFilterValues[key];\n } else {\n this.latestFilterValues[key] = value;\n }\n }\n\n onFilterClear(field: string | undefined): void {\n if (!field) return;\n\n delete this.latestFilterValues[field];\n this.clearedFields.add(field);\n }\n\n onFilterValueChange(\n field: string | undefined,\n filterModel: any | null,\n value: any,\n ): void {\n if (!field) return;\n\n const isEmpty =\n value === null ||\n value === undefined ||\n value === '' ||\n (Array.isArray(value) && value.length === 0);\n\n if (isEmpty) {\n delete this.latestFilterValues[field];\n } else {\n this.latestFilterValues[field] = value;\n }\n\n if (filterModel) {\n filterModel.value = value;\n\n if (\n Array.isArray(filterModel.constraints) &&\n filterModel.constraints.length > 0\n ) {\n filterModel.constraints[0].value = value;\n }\n }\n }\n\n onNumberFilterChange(field: string | undefined, value: any): void {\n if (!field) return;\n\n const isEmpty =\n value === null ||\n value === undefined ||\n value === '' ||\n (Array.isArray(value) && value.length === 0);\n\n if (isEmpty) {\n delete this.latestFilterValues[field];\n } else {\n this.latestFilterValues[field] = value;\n }\n }\n\n private findColumnByField(field: string): TableColumn | undefined {\n return this.columns.find(\n (c) => c.code === field || (c.code && field.startsWith(c.code + '.')),\n );\n }\n\n filterColumn(event: any): void {\n const filters = event?.filters;\n\n if (!filters) {\n if (this.isLazy) {\n this.resetToFirstPage();\n this.onFilterColumn.emit(event);\n this.emitLazyLoad();\n }\n return;\n }\n\n const isNullish = (v: any) => v === null || v === undefined || v === '';\n const nextFilterValues: { [field: string]: any } = {\n ...this.latestFilterValues,\n };\n\n Object.keys(filters).forEach((field) => {\n const meta = filters[field];\n const normalizeMeta = (m: any) =>\n Array.isArray(m) && m.length > 0 ? m[0] : m;\n\n const m = normalizeMeta(meta);\n const col = this.findColumnByField(field);\n const wasCleared = this.clearedFields.has(field);\n\n if (!m && !col) return;\n\n if (wasCleared) {\n delete nextFilterValues[field];\n\n if (\n col?.type === TableTypeEnum.COMPOSED &&\n col.code &&\n col.composedNames\n ) {\n col.composedNames.forEach((name) => {\n delete nextFilterValues[`${col.code}.${name}`];\n });\n }\n\n if (m) {\n m.value = null;\n\n if (Array.isArray(m.constraints) && m.constraints.length > 0) {\n m.constraints.forEach((c: any) => (c.value = null));\n }\n }\n\n this.clearedFields.delete(field);\n return;\n }\n\n if (col && col.type === TableTypeEnum.COMPOSED) {\n const composedValues: any = {};\n\n col.composedNames?.forEach((name) => {\n const key = `${col.code}.${name}`;\n\n if (this.clearedFields.has(key)) {\n delete nextFilterValues[key];\n this.clearedFields.delete(key);\n return;\n }\n\n const val = nextFilterValues[key];\n const empty =\n isNullish(val) || (Array.isArray(val) && val.length === 0);\n\n if (!empty) {\n composedValues[name] = val;\n }\n });\n\n if (Object.keys(composedValues).length === 0) {\n if (m) {\n m.value = null;\n if (Array.isArray(m.constraints)) {\n m.constraints.forEach((c: any) => (c.value = null));\n }\n }\n } else {\n m.value = composedValues;\n }\n\n return;\n }\n\n if (!m) return;\n\n let value: any = m.value;\n\n if (Array.isArray(m.constraints) && m.constraints.length > 0) {\n const cVal = m.constraints[0].value;\n if (!isNullish(cVal)) {\n value = cVal;\n }\n }\n\n const cached = nextFilterValues[field];\n const hasCached =\n !isNullish(cached) && (!Array.isArray(cached) || cached.length > 0);\n\n if (\n (isNullish(value) || (Array.isArray(value) && value.length === 0)) &&\n hasCached\n ) {\n value = cached;\n }\n\n const isEmpty =\n isNullish(value) || (Array.isArray(value) && value.length === 0);\n\n if (isEmpty) {\n delete nextFilterValues[field];\n m.value = null;\n\n if (Array.isArray(m.constraints) && m.constraints.length > 0) {\n m.constraints[0].value = null;\n }\n\n return;\n }\n\n let emitValue: any = value;\n\n if (\n col &&\n (col.type === TableTypeEnum.DATE || col.type === TableTypeEnum.DATETIME)\n ) {\n const d = this.parseAnyDate(value);\n emitValue =\n col.type === TableTypeEnum.DATE\n ? this.formatDateWithColumn(d, col)\n : this.formatDateTimeWithColumn(d, col);\n }\n\n nextFilterValues[field] = emitValue;\n m.value = emitValue;\n\n if (Array.isArray(m.constraints) && m.constraints.length > 0) {\n m.constraints[0].value = emitValue;\n }\n });\n\n this.latestFilterValues = nextFilterValues;\n\n if (this.isLazy) {\n this.resetToFirstPage();\n this.onFilterColumn.emit(event);\n this.emitLazyLoad();\n } else if (this.dt) {\n const current = (this.dt.filteredValue ?? this.dt.value ?? []) as any[];\n this.totalRecords = current.length;\n this.dt.first = 0;\n this.filteredData.emit([...current]);\n }\n }\n\n changePage(event: any): void {\n const page = event.page ?? Math.floor((event.first || 0) / event.rows);\n const rows = event.rows;\n const first = event.first ?? page * rows;\n\n this.rows = rows;\n this.currentPage = page;\n this.first = first;\n\n if (this.isLazy) {\n this.onPageChange.emit({ page, rows });\n this.emitLazyLoad();\n }\n }\n\n sortColumn(event: any): void {\n if (!this.isLazy) return;\n\n let field = event.field;\n const col = this.columns.find((c) => c.code === field);\n\n if (col && col.type === TableTypeEnum.COMPOSED) {\n let textProp: string | undefined;\n\n if (col.composedNames && col.composedTypes) {\n const idx = col.composedTypes.findIndex(\n (t) => t === TableTypeEnum.STRING,\n );\n\n if (idx >= 0 && idx < col.composedNames.length) {\n textProp = col.composedNames[idx];\n }\n }\n\n if (!textProp && col.composedNames?.length) {\n textProp = col.composedNames[0];\n }\n\n if (textProp) {\n field = `${field}.${textProp}`;\n }\n }\n\n this.currentSortField = field ?? null;\n this.currentSortOrder = (event.order as 1 | -1) ?? null;\n\n this.resetToFirstPage();\n this.onSortColumn.emit({ ...event, field });\n this.emitLazyLoad();\n }\n\n private parseDate_ddMMyyyy(dateString: string): Date | null {\n const parts = dateString.split('/');\n\n if (parts.length === 3) {\n const day = parseInt(parts[0], 10);\n const month = parseInt(parts[1], 10) - 1;\n const year = parseInt(parts[2], 10);\n const date = new Date(year, month, day);\n\n return isNaN(date.getTime()) ? null : date;\n }\n\n return null;\n }\n\n onChange(event: Event, id: number, key: any): void {\n const target = event.target as HTMLInputElement;\n this.changeHandler(id, key, target.value);\n }\n\n changeHandler(id: number, key: any, value: any): void {\n const column = this.columns.find((item) => item.code === key);\n\n if (!this.map.get(id)) {\n if (column?.type === TableTypeEnum.DATE) {\n this.dataMap.set(key, this.parseDate_ddMMyyyy(value));\n } else {\n this.dataMap.set(key, value);\n }\n\n this.map.set(id, new Map(this.dataMap));\n } else {\n const mapItem = this.map.get(id) as Map<any, any>;\n\n if (column?.type === TableTypeEnum.DATE) {\n mapItem.set(key, this.parseDate_ddMMyyyy(value));\n } else {\n mapItem.set(key, value);\n }\n }\n }\n\n getColumnFilterType(column: TableColumn): string {\n switch (column.type) {\n case TableTypeEnum.STRING:\n return 'text';\n case TableTypeEnum.AMOUNT:\n case TableTypeEnum.NUMBER:\n return 'numeric';\n case TableTypeEnum.DATE:\n case TableTypeEnum.DATETIME:\n return 'date';\n case TableTypeEnum.MULTISELECT:\n return 'multiSelect';\n case TableTypeEnum.BOOLEAN:\n return 'boolean';\n case TableTypeEnum.COMPOSED:\n return 'composed';\n default:\n return 'text';\n }\n }\n\n isEditable(key: string | undefined): boolean {\n if (!key) return false;\n\n const column = this.columns.find((item) => item.code === key);\n return column?.isEditable !== false;\n }\n\n isMultiSelect(key: any): boolean {\n const column = this.columns.find((item) => item.code === key);\n\n if (\n column?.type === TableTypeEnum.MULTISELECT &&\n column.options &&\n column.code !== undefined\n ) {\n this.optionEntries = new Map([\n [column.code as string, Object.values(column.options)],\n ]);\n this.optionValues = this.optionEntries.get(key) || [];\n return true;\n }\n\n return false;\n }\n\n isDatePicker(key: any): boolean {\n const t = this.columns.find((item) => item.code === key)?.type;\n return t === TableTypeEnum.DATE || t === TableTypeEnum.DATETIME;\n }\n\n isDateTimePicker(key: any): boolean {\n const t = this.columns.find((item) => item.code === key)?.type;\n return t === TableTypeEnum.DATETIME;\n }\n\n filterGlobal(event: Event): void {\n const target = event.target as HTMLInputElement;\n const value = (target.value || '').toLowerCase();\n\n if (this.isLazy) {\n this.searchValue = value;\n this.resetToFirstPage();\n this.search.emit(value);\n this.emitLazyLoad();\n return;\n }\n\n if (!value) {\n const allData = [...(this.data ?? [])];\n\n this.dt.value = allData;\n this.totalRecords = allData.length;\n this.dt.first = 0;\n this.filteredData.emit(allData);\n return;\n }\n\n const filteredData = (this.data ?? []).filter((item) =>\n (this.globalFilterFields ?? []).some((field) => {\n const column = this.columns?.find((col) => col.code === field);\n if (!column) return false;\n\n const cell = item?.[field];\n\n if (column.type === TableTypeEnum.DATE) {\n return this.formatDateWithColumn(this.parseAnyDate(cell), column)\n .toLowerCase()\n .includes(value);\n }\n\n if (column.type === TableTypeEnum.DATETIME) {\n return this.formatDateTimeWithColumn(this.parseAnyDate(cell), column)\n .toLowerCase()\n .includes(value);\n }\n\n if (\n column.type === TableTypeEnum.AMOUNT ||\n column.type === TableTypeEnum.NUMBER\n ) {\n return String(cell ?? '')\n .toLowerCase()\n .includes(value);\n }\n\n if (column.type === TableTypeEnum.COMPOSED) {\n return this.filterComposedColumn(cell, value);\n }\n\n return String(cell ?? '')\n .toLowerCase()\n .includes(value);\n }),\n );\n\n this.dt.value = filteredData;\n this.totalRecords = filteredData.length;\n this.dt.first = 0;\n this.filteredData.emit([...filteredData]);\n }\n\n private filterComposedColumn(composedData: any, value: string): boolean {\n if (!composedData) return false;\n\n return Object.keys(composedData).some((key) => {\n const cellValue = composedData[key];\n return typeof cellValue === 'string'\n ? cellValue.toLowerCase().includes(value)\n : false;\n });\n }\n\n public parseAnyDate(input: any): Date | null {\n if (input === null || input === undefined || input === '') return null;\n if (input instanceof Date) return isNaN(input.getTime()) ? null : input;\n\n if (typeof input === 'number') {\n const d = new Date(input);\n return isNaN(d.getTime()) ? null : d;\n }\n\n if (typeof input === 'string') {\n const s = input.trim();\n const isoTry = new Date(s);\n\n if (!isNaN(isoTry.getTime())) return isoTry;\n\n if (s.includes('/')) {\n const parts = s.split(' ')[0].split('/');\n\n if (parts.length === 3) {\n const day = parseInt(parts[0], 10);\n const month = parseInt(parts[1], 10) - 1;\n const year = parseInt(parts[2], 10);\n const d = new Date(year, month, day);\n\n return isNaN(d.getTime()) ? null : d;\n }\n }\n }\n\n return null;\n }\n\n private formatWithPattern(d: Date | null, pattern: string): string {\n if (!d) return '';\n\n const pad2 = (n: number) => String(n).padStart(2, '0');\n\n const map: Record<string, string> = {\n dd: pad2(d.getDate()),\n MM: pad2(d.getMonth() + 1),\n yyyy: String(d.getFullYear()),\n HH: pad2(d.getHours()),\n mm: pad2(d.getMinutes()),\n ss: pad2(d.getSeconds()),\n };\n\n return pattern\n .replace(/yyyy/g, map.yyyy)\n .replace(/dd/g, map.dd)\n .replace(/MM/g, map.MM)\n .replace(/HH/g, map.HH)\n .replace(/mm/g, map.mm)\n .replace(/ss/g, map.ss);\n }\n\n formatDate(date: any): string {\n const d = this.parseAnyDate(date);\n return this.formatWithPattern(d, 'dd/MM/yyyy');\n }\n\n formatDateWithColumn(d: Date | null, col: TableColumn): string {\n const fmt = col.dateFormat?.trim() || 'dd/MM/yyyy';\n return this.formatWithPattern(d, fmt);\n }\n\n formatDateTimeWithColumn(d: Date | null, col: TableColumn): string {\n const fmt = col.dateTimeFormat?.trim() || 'dd/MM/yyyy HH:mm:ss';\n return this.formatWithPattern(d, fmt);\n }\n\n private calculateColumnWidth(col: TableColumn): string {\n const calculatedWidth = calculateTextWidth(col, col.title);\n const totalWidth = calculatedWidth + this.iconWidth + 20;\n return `${totalWidth}px`;\n }\n\n getHeaderWidth(col: TableColumn): string {\n return col.width ? col.width : this.calculateColumnWidth(col);\n }\n\n clear(table: Table): void {\n table.clear();\n this.searchValue = '';\n this.latestFilterValues = {};\n this.clearedFields.clear();\n this.currentSortField = null;\n this.currentSortOrder = null;\n\n Object.keys(this.filters).forEach((key) => {\n if (this.filters[key]) this.filters[key].value = [];\n });\n\n if (this.isLazy) {\n this.resetToFirstPage();\n this.onFilterColumn.emit({ cleared: true });\n this.emitLazyLoad();\n return;\n }\n\n if (this.dt) {\n const allData = [...(this.data ?? [])];\n\n this.dt.value = allData;\n this.totalRecords = allData.length;\n this.dt.first = 0;\n this.filteredData.emit(allData);\n }\n }\n\n private initializePagination(): void {\n if (this.isPaginated) {\n if (!this.rowsPerPage || this.rowsPerPage.length === 0) {\n this.rowsPerPage = [10, 20, 30];\n }\n\n this.rows = this.rowsPerPage[0];\n this.currentPage = 0;\n this.first = 0;\n }\n }\n\n getCurrencySymbol(column: TableColumn): string | undefined {\n return column.type === TableTypeEnum.AMOUNT &&\n column.currency &&\n this.isValidCurrencyCode(column.currency)\n ? column.currency\n : undefined;\n }\n\n private isValidCurrencyCode(currencyCode: string): boolean {\n return this.validCurrencyCodes.includes(currencyCode);\n }\n\n exportExcel(): void {\n this.exportExcelEvent.emit();\n }\n\n exportPdf(): void {\n this.exportPdfEvent.emit();\n }\n\n getImageStyle(style: TitleStyle | ImageStyle | undefined): {\n [key: string]: string;\n } {\n if (!style) return {};\n\n const imageStyle: { [key: string]: string } = {\n width: (style as ImageStyle).width || 'auto',\n height: (style as ImageStyle).height || 'auto',\n };\n\n if ((style as ImageStyle).margin) {\n imageStyle.margin = (style as ImageStyle).margin!;\n }\n\n if ((style as ImageStyle).marginLeft) {\n imageStyle.marginLeft = (style as ImageStyle).marginLeft!;\n }\n\n if ((style as ImageStyle).marginRight) {\n imageStyle.marginRight = (style as ImageStyle).marginRight!;\n }\n\n if ((style as ImageStyle).marginTop) {\n imageStyle.marginTop = (style as ImageStyle).marginTop!;\n }\n\n if ((style as ImageStyle).marginBottom) {\n imageStyle.marginBottom = (style as ImageStyle).marginBottom!;\n }\n\n return imageStyle;\n }\n\n getTitleStyle(style: TitleStyle | ImageStyle | undefined): {\n [key: string]: string;\n } {\n if (!style) return {};\n\n return {\n color: style.color || 'inherit',\n fontSize: (style as TitleStyle).fontSize || 'inherit',\n textAlign: (style as TitleStyle).position || 'left',\n };\n }\n\n formatNumber(\n value: number,\n decimalPlaces?: number,\n thousandSeparator: 'comma' | 'space' = 'comma',\n decimalSeparator: 'comma' | 'dot' = 'dot',\n ): string {\n if (value === null || value === undefined || isNaN(value)) return '';\n\n let formattedNumber =\n decimalPlaces !== undefined\n ? value.toFixed(decimalPlaces)\n : value.toString();\n\n if (decimalSeparator === 'comma') {\n formattedNumber = formattedNumber.replace('.', ',');\n }\n\n if (thousandSeparator && Math.abs(value) >= 1000) {\n const parts = formattedNumber.split(\n decimalSeparator === 'comma' ? ',' : '.',\n );\n\n parts[0] = parts[0].replace(\n /\\B(?=(\\d{3})+(?!\\d))/g,\n thousandSeparator === 'comma' ? ',' : ' ',\n );\n\n formattedNumber = parts.join(decimalSeparator === 'comma' ? ',' : '.');\n }\n\n return formattedNumber;\n }\n\n isActionVisible(action: PTTableActionConfig, row: any): boolean {\n const v = action?.visible;\n\n if (v === undefined || v === null) return true;\n\n return typeof v === 'function' ? !!v(row) : !!v;\n }\n\n isActionDisabled(action: PTTableActionConfig, row: any): boolean {\n const d = action.disabled;\n\n if (d === undefined || d === null) return false;\n\n return typeof d === 'function' ? !!d(row) : !!d;\n }\n\n getComposedCellStyle(\n col: TableColumn,\n composedName: string,\n row: any,\n ): { [key: string]: string } {\n const extendedCol = col as ExtendedTableColumn;\n const styleConfig = extendedCol?.composedCellStyles?.[composedName];\n\n if (!styleConfig) {\n return {};\n }\n\n return typeof styleConfig === 'function'\n ? (styleConfig(row) ?? {})\n : styleConfig;\n }\n\n mergeStyles(...styles: Array<{ [key: string]: string } | null | undefined>): {\n [key: string]: string;\n } {\n return styles.reduce<{ [key: string]: string }>((acc, style) => {\n if (!style) {\n return acc;\n }\n\n return {\n ...acc,\n ...style,\n };\n }, {});\n }\n\n getMergedComposedTextStyle(\n col: TableColumn,\n composedName: string,\n row: any,\n ): { [key: string]: string } {\n return this.mergeStyles(\n this.getTitleStyle(col.composedStyles?.[composedName]),\n this.getComposedCellStyle(col, composedName, row),\n );\n }\n\n getMergedComposedImageStyle(\n col: TableColumn,\n composedName: string,\n row: any,\n ): { [key: string]: string } {\n return this.mergeStyles(\n this.getImageStyle(col.composedStyles?.[composedName]),\n this.getComposedCellStyle(col, composedName, row),\n );\n }\n\n getTagValue(col: TableColumn, row: any): string {\n const extendedCol = col as ExtendedTableColumn;\n const tagValue = extendedCol.tagValue;\n\n if (typeof tagValue === 'function') {\n return tagValue(row) ?? '';\n }\n\n if (tagValue !== undefined && tagValue !== null) {\n return String(tagValue);\n }\n\n return String(col.code ? row?.[col.code] : '');\n }\n\n getTagSeverity(col: TableColumn, row: any): SeverityEnum {\n const extendedCol = col as ExtendedTableColumn;\n const tagSeverity = extendedCol.tagSeverity;\n\n if (typeof tagSeverity === 'function') {\n return tagSeverity(row) ?? SeverityEnum.INFO;\n }\n\n return tagSeverity ?? SeverityEnum.INFO;\n }\n\n getTagIcon(col: TableColumn, row: any): string | undefined {\n const extendedCol = col as ExtendedTableColumn;\n const tagIcon = extendedCol.tagIcon;\n\n if (typeof tagIcon === 'function') {\n return tagIcon(row) || undefined;\n }\n\n return tagIcon || undefined;\n }\n\n isTagRounded(col: TableColumn): boolean {\n return (col as ExtendedTableColumn).tagRounded !== false;\n }\n\n getActionTooltip(action: PTTableActionConfig, row: any): string {\n if (!action?.tooltip) {\n return '';\n }\n\n return action.tooltip;\n }\n\n getDefaultActionTooltip(code: string): string {\n switch (code) {\n case 'delete':\n return 'Supprimer';\n case 'edit':\n return 'Mod