UNPKG

ms-data-grid

Version:

A powerful, customizable Angular data grid component with advanced features like sorting, filtering, pagination, column pinning, and taskbar actions. Perfect for enterprise applications.

1 lines 511 kB
{"version":3,"file":"ms-data-grid.mjs","sources":["../../../projects/data-grid/src/lib/data-grid.service.ts","../../../projects/data-grid/src/lib/data-grid/statuses.ts","../../../projects/data-grid/src/lib/services/split-columns.service.ts","../../../projects/data-grid/src/lib/services/common.service.ts","../../../projects/data-grid/src/lib/services/swap-columns.service.ts","../../../projects/data-grid/src/lib/services/copy-service.service.ts","../../../projects/data-grid/src/lib/pipes/filter.pipe.ts","../../../projects/data-grid/src/lib/data-grid/data-grid.component.ts","../../../projects/data-grid/src/lib/data-grid/data-grid.component.html","../../../projects/data-grid/src/lib/directives/draggable-header.directive.ts","../../../projects/data-grid/src/lib/data-grid.module.ts","../../../projects/data-grid/src/public-api.ts","../../../projects/data-grid/src/ms-data-grid.ts"],"sourcesContent":["import { Injectable } from '@angular/core';\r\n\r\n@Injectable({\r\n providedIn: 'root'\r\n})\r\nexport class DataGridService {\r\n\r\n constructor() { }\r\n}\r\n","export const STATUSES_BADGE_MAP: { [key: string]: string } = {\r\n // Success – Green\r\n 'active': 'badge badge-success',\r\n 'approved': 'badge badge-success',\r\n 'accepted': 'badge badge-success',\r\n 'completed': 'badge badge-success',\r\n 'evaluated': 'badge badge-success',\r\n 'assigned': 'badge badge-success',\r\n 'scrap': 'badge badge-success',\r\n 'move-available': 'badge badge-success',\r\n 'move-assigned': 'badge badge-success',\r\n\r\n // Warning – Yellow/Amber\r\n 'contract': 'badge badge-warning',\r\n 'warranty': 'badge badge-warning',\r\n 'scheduled': 'badge badge-warning',\r\n 'leased': 'badge badge-warning',\r\n 'disposed': 'badge badge-warning',\r\n 'maintenance': 'badge badge-warning',\r\n 'assigning start': 'badge badge-warning',\r\n 'evaluation start': 'badge badge-warning',\r\n 'to be start assigning': 'badge badge-warning',\r\n 'pending': 'badge badge-warning',\r\n 'leave': 'badge badge-warning',\r\n\r\n // Danger – Red\r\n 'inactive': 'badge badge-danger',\r\n 'rejected': 'badge badge-danger',\r\n 'unassigned': 'badge badge-danger',\r\n 'trashed': 'badge badge-danger',\r\n 'onhold': 'badge badge-danger',\r\n 'assigning stop': 'badge badge-danger',\r\n 'evaluation stop': 'badge badge-danger',\r\n 'unavailable': 'badge badge-danger',\r\n 'move-error': 'badge badge-danger',\r\n 'failed': 'badge badge-danger',\r\n 'absent': 'badge badge-danger',\r\n\r\n // Info – Blue\r\n 'insurance': 'badge badge-info',\r\n 'pastdue': 'badge badge-info',\r\n\r\n // Dark – Neutral/Other\r\n 'expired': 'badge badge-dark',\r\n 'draft': 'badge badge-dark',\r\n 'present': 'badge badge-success'\r\n};\r\n","import { Injectable } from '@angular/core';\r\n\r\n@Injectable({\r\n providedIn: 'root',\r\n})\r\nexport class SplitColumnsService {\r\n prepareColumns(columns: any[], containerWidth: number): any {\r\n const left: any[] = [];\r\n const center: any[] = [];\r\n const right: any[] = [];\r\n\r\n for (const col of columns) {\r\n if (col.children?.length) {\r\n const leftChildren: any[] = [];\r\n const centerChildren: any[] = [];\r\n const rightChildren: any[] = [];\r\n\r\n for (const child of col.children) {\r\n if (child.is_visible === false) continue;\r\n\r\n const pinned = child.pinned ?? col.pinned ?? null;\r\n const childWithPinned = { ...child, pinned };\r\n\r\n if (pinned === 'left') leftChildren.push(childWithPinned);\r\n else if (pinned === 'right') rightChildren.push(childWithPinned);\r\n else centerChildren.push(childWithPinned);\r\n }\r\n\r\n if (leftChildren.length) {\r\n left.push({ header: col.header, children: leftChildren, id: col?.id || col?._id });\r\n }\r\n if (centerChildren.length) {\r\n center.push({\r\n header: col.header,\r\n children: centerChildren,\r\n id: col.id || col._id,\r\n });\r\n }\r\n if (rightChildren.length) {\r\n right.push({\r\n header: col.header,\r\n children: rightChildren,\r\n id: col.id || col._id,\r\n });\r\n }\r\n } else if (col.is_visible !== false) {\r\n const pinned = col.pinned ?? null;\r\n if (pinned === 'left') left.push(col);\r\n else if (pinned === 'right') right.push(col);\r\n else center.push(col);\r\n }\r\n }\r\n\r\n return { left, center, right };\r\n }\r\n\r\n setColumnsQuery(columns: any[]) {\r\n for (const col of columns) {\r\n // if (col.children?.length) {\r\n // for (const child of col.children) {\r\n // if (!child?.query?.firt_value && !child?.query?._ids?.length) {\r\n // child['query'] = {\r\n // first_condition: 'contain',\r\n // first_value: null,\r\n // condition: 'none',\r\n // second_condition: 'contain',\r\n // second_value: null,\r\n // _ids: [],\r\n // };\r\n // }\r\n // }\r\n // }\r\n // if (!col?.query?.firt_value && !col?.query?._ids?.length) {\r\n // col.query = {\r\n // first_condition: 'contain',\r\n // first_value: null,\r\n // condition: 'none',\r\n // second_condition: 'contain',\r\n // second_value: null,\r\n // _ids: [],\r\n // };\r\n // }\r\n }\r\n\r\n console.log('Updated Columns: ', columns);\r\n\r\n return columns;\r\n }\r\n\r\n assignDefaultWidths(columns: any[], containerWidth: number): any[] {\r\n const visibleLeafCols = this.getVisibleLeafColumns(columns);\r\n\r\n if (!visibleLeafCols.length) return columns;\r\n\r\n let defaultWidth = Math.floor(containerWidth / visibleLeafCols.length);\r\n if (defaultWidth < 80) defaultWidth = 80;\r\n\r\n const cloneColumns = (cols: any[]): any[] =>\r\n cols.map((col) => {\r\n if (col.children?.length) {\r\n const newChildren = col.children.map((child: any) => {\r\n // If visible → dynamic default width\r\n // If invisible → fixed 150px\r\n if (!child.width) {\r\n if (child.is_visible === false) {\r\n return { ...child, width: 150 };\r\n } else {\r\n return { ...child, width: defaultWidth };\r\n }\r\n }\r\n return { ...child };\r\n });\r\n\r\n return { ...col, children: newChildren };\r\n }\r\n\r\n if (!col.width) {\r\n if (col.is_visible === false) {\r\n return { ...col, width: 150 };\r\n } else {\r\n return { ...col, width: defaultWidth };\r\n }\r\n }\r\n\r\n return { ...col };\r\n });\r\n\r\n return cloneColumns(columns);\r\n}\r\n\r\n\r\n private getVisibleLeafColumns(columns: any[]): any[] {\r\n const result: any[] = [];\r\n\r\n for (const col of columns) {\r\n if (col.children?.length) {\r\n const visibleChildren = col.children.filter(\r\n (c: any) => c.is_visible !== false\r\n );\r\n result.push(...visibleChildren);\r\n } else if (col.is_visible !== false) {\r\n result.push(col);\r\n }\r\n }\r\n\r\n return result;\r\n }\r\n}\r\n","import { Injectable } from '@angular/core';\r\n\r\n@Injectable({\r\n providedIn: 'root'\r\n})\r\nexport class CommonService {\r\n\r\n constructor() { }\r\n gethasVisibleColumns(columns: any[]): boolean {\r\n const checkVisible = (columns: any[]): boolean => {\r\n return columns.some((col) => {\r\n if (col?.is_visible) return true;\r\n if (col?.children?.length) {\r\n return checkVisible(col.children);\r\n }\r\n return false;\r\n });\r\n };\r\n return checkVisible(columns);\r\n }\r\n\r\n gethasInVisibleColumns(columns: any[]): boolean {\r\n const checkVisible = (columns: any[]): boolean => {\r\n return columns.some((col) => {\r\n if (!col?.is_visible) return true;\r\n if (col?.children?.length) {\r\n return checkVisible(col.children);\r\n }\r\n return false;\r\n });\r\n };\r\n return checkVisible(columns);\r\n }\r\n\r\n\r\n getTotalColumnsLength(columns: any[]): number {\r\n let count = 0;\r\n\r\n columns.forEach(col => {\r\n if (col.children && Array.isArray(col.children) && col.children.length) {\r\n count += col.children.length; // count children instead of parent\r\n } else {\r\n count += 1; // count parent directly\r\n }\r\n });\r\n\r\n return count;\r\n }\r\n\r\n gethasRightPinnedColumns(columns: any[]): boolean {\r\n const checkPinnedRight = (columns: any[]): boolean => {\r\n return columns.some((col) => {\r\n if (col?.pinned === 'right' && col?.is_visible) return true;\r\n if (col?.children?.length) {\r\n return checkPinnedRight(col.children);\r\n }\r\n return false;\r\n });\r\n };\r\n return checkPinnedRight(columns);\r\n }\r\n\r\n gethasLeftPinnedColumns(columns: any[]): boolean {\r\n const checkPinnedRight = (columns: any[]): boolean => {\r\n return columns.some((col) => {\r\n if (col?.pinned === 'left' && col?.is_visible) return true;\r\n if (col?.children?.length) {\r\n return checkPinnedRight(col.children);\r\n }\r\n return false;\r\n });\r\n };\r\n return checkPinnedRight(columns);\r\n }\r\n\r\n\r\n getExpandedRowCount(data: any[]): number {\r\n let groupCount = 0;\r\n let rowCount = 0;\r\n\r\n data.forEach(group => {\r\n if (group?.isGroup) {\r\n groupCount++;\r\n\r\n if (group?.isExpand && Array.isArray(group?.children)) {\r\n group.children.forEach((child: any) => {\r\n if (child.isGroup) {\r\n if (child.isExpand && Array.isArray(child.children)) {\r\n rowCount += child.children.length;\r\n }\r\n } else {\r\n rowCount++;\r\n }\r\n });\r\n }\r\n }\r\n });\r\n\r\n if (groupCount === 0) {\r\n return data.length;\r\n }\r\n\r\n return groupCount + rowCount;\r\n }\r\n\r\n\r\ngetFiltersFromColumns(columns: any[], filtersConfig:any []): any[] {\r\n const result: any[] = [];\r\n \r\n const checkColumn = (col: any) => {\r\n const hasFirstValue = col.query?.first_value != null && col.query?.first_value !== \"\" && filtersConfig.some((ele)=> ele.field == col.field);\r\n const hasIds = Array.isArray(col.query?._ids) && col.query._ids.length > 0 && filtersConfig.some((ele)=> ele.field == col.field);\r\n if (hasFirstValue || hasIds) {\r\n result.push({\r\n search: col.search ?? \"\",\r\n field: col.field,\r\n type: col.type,\r\n _ids: col.type == 'dropdown'? col.query?._ids: [],\r\n query: col.type == 'dropdown'? null : (col?.query?.first_value ? col?.query : null)\r\n });\r\n }\r\n \r\n if (Array.isArray(col.children) && col.children.length > 0) {\r\n col.children.forEach(checkColumn);\r\n }\r\n };\r\n columns.forEach(checkColumn);\r\n \r\n return result;\r\n}\r\n\r\nasync applyFiltersToColumns(columns: any[], filters: any[]): Promise<any[]> {\r\n debugger\r\n for (const col of columns) {\r\n if (!col.query) {\r\n col.query = {\r\n first_value: null,\r\n second_value: null,\r\n first_condition: col.type !== 'date'? 'contain': 'equal',\r\n second_condition: col.type !== 'date'? 'contain': 'equal',\r\n condition: 'none',\r\n _ids: []\r\n };\r\n }\r\n\r\n const filter = filters.find(f => f.field === col.field);\r\n\r\n if (filter) {\r\n if (col.type === 'dropdown') {\r\n col.filterValue = filter._ids ?? [];\r\n col.search = filter.search ?? '';\r\n col.query._ids = filter._ids ?? [];\r\n } else {\r\n col.filterValue = filter.search ?? null;\r\n col.search = filter.search ?? '';\r\n col.query.first_value = filter.query?.first_value ?? null;\r\n col.query.second_value = filter.query?.second_value ?? null;\r\n col.query.first_condition = filter.query?.first_condition ?? filter.type !== 'date'? 'contain': 'equal';\r\n col.query.second_condition = filter.query?.second_condition ?? filter.type !== 'date'? 'contain': 'equal';\r\n col.query.condition = filter.query?.condition ?? 'none';\r\n }\r\n }\r\n if (Array.isArray(col.children) && col.children.length > 0) {\r\n col.children = await this.applyFiltersToColumns(col.children, filters);\r\n }\r\n }\r\n return columns;\r\n}\r\n\r\n\r\n\r\n\r\n\r\nactiveFilteredColumns: any[] = [];\r\nupdateActiveFilteredColumns(columns: any[]): any[] {\r\n const collectFiltered = (cols: any[]): any[] => {\r\n let result: any[] = [];\r\n\r\n for (let i = 0; i < cols.length; i++) {\r\n const col = cols[i];\r\n if (col.children && col.children.length > 0) {\r\n result = result.concat(collectFiltered(col.children));\r\n }\r\n\r\n if (col.query) {\r\n const hasDropdownFilter =\r\n Array.isArray(col.query._ids) && col.query._ids.length > 0;\r\n\r\n const hasValueFilter =\r\n (col.query.first_value && col.query.first_value !== '') ||\r\n (col.query.second_value && col.query.second_value !== '');\r\n\r\n if (hasDropdownFilter || hasValueFilter) {\r\n result.push(col);\r\n }\r\n }\r\n }\r\n return result;\r\n };\r\n this.activeFilteredColumns = [...collectFiltered(columns)];\r\n return this.activeFilteredColumns;\r\n}\r\n\r\n\r\n\r\nhasFieldChanged(current: any, original: any, type: string): boolean {\r\n switch (type) {\r\n case 'number':\r\n return Number(current) !== Number(original);\r\n\r\n case 'string':\r\n return String(current || '') !== String(original || '');\r\n\r\n case 'dropdown':\r\n if (typeof current === 'object' && typeof original === 'object') {\r\n return current?.id !== original?.id || current?.value !== original?.value;\r\n }\r\n return current !== original;\r\n\r\n case 'boolean':\r\n return Boolean(current) !== Boolean(original);\r\n\r\n case 'date':\r\n const currentDate = new Date(current).getTime();\r\n const originalDate = new Date(original).getTime();\r\n return isNaN(currentDate) || isNaN(originalDate)\r\n ? current !== original\r\n : currentDate !== originalDate;\r\n\r\n default:\r\n return JSON.stringify(current) !== JSON.stringify(original);\r\n }\r\n }\r\n\r\n\r\n}\r\n\r\n\r\n\r\n\r\n","import { Injectable } from '@angular/core';\r\n\r\n@Injectable({\r\n providedIn: 'root',\r\n})\r\nexport class SwapColumnsService {\r\n constructor() {}\r\n\r\n deepClone(obj: any) {\r\n return JSON.parse(JSON.stringify(obj));\r\n }\r\n\r\n flattenColumnsWithPaths(columns: any[]): { col: any; path: number[] }[] {\r\n const result: { col: any; path: number[] }[] = [];\r\n\r\n const recurse = (cols: any[], currentPath: number[] = []) => {\r\n cols.forEach((col, index) => {\r\n const path = [...currentPath, index];\r\n result.push({ col, path });\r\n\r\n if (Array.isArray(col.children)) {\r\n recurse(col.children, path);\r\n }\r\n });\r\n };\r\n\r\n recurse(columns);\r\n return result;\r\n }\r\n\r\n findColumnByIndex(\r\n columns: any[],\r\n index: number,\r\n flat: any[],\r\n _path: number[][] = [],\r\n currentPath: number[] = []\r\n ): { column: any; parent: any[]; path: number[] } | null {\r\n for (let i = 0; i < columns.length; i++) {\r\n const col = columns[i];\r\n const nextPath = [...currentPath, i];\r\n\r\n if (flat[index] === col) {\r\n return { column: col, parent: columns, path: nextPath };\r\n }\r\n\r\n if (Array.isArray(col?.children)) {\r\n const result = this.findColumnByIndex(\r\n col?.children,\r\n index,\r\n flat,\r\n _path,\r\n nextPath\r\n );\r\n if (result) return result;\r\n }\r\n }\r\n\r\n return null;\r\n }\r\n\r\n insertAtPath(columns: any[], path: number[], newColumn: any): any[] {\r\n const cloned = this.deepClone(columns);\r\n let current = cloned;\r\n\r\n for (let i = 0; i < path.length - 1; i++) {\r\n if (!current[path[i]]?.children) {\r\n current[path[i]]!.children = [];\r\n }\r\n\r\n current = current[path[i]]?.children;\r\n }\r\n\r\n const insertIndex = path[path.length - 1];\r\n current.splice(insertIndex, 0, newColumn);\r\n return cloned;\r\n }\r\n\r\n removeAtPath(columns: any[], path: number[]): any[] {\r\n const cloned = this.deepClone(columns);\r\n let current = cloned;\r\n\r\n for (let i = 0; i < path.length - 1; i++) {\r\n if (!current[path[i]]?.children) {\r\n return cloned; // Safety: path is invalid\r\n }\r\n current = current[path[i]]?.children;\r\n }\r\n\r\n const removeIndex = path[path.length - 1];\r\n if (Array.isArray(current) && current.length > removeIndex) {\r\n current.splice(removeIndex, 1);\r\n }\r\n\r\n return cloned;\r\n }\r\n\r\n swapColumnsInTree(\r\n currentColumn: any,\r\n columns: any[],\r\n fromIndex: number,\r\n toIndex: number\r\n ): any[] {\r\n const flat = this.flattenColumnsWithPaths(columns);\r\n\r\n if (\r\n fromIndex < 0 ||\r\n toIndex < 0 ||\r\n fromIndex >= flat.length ||\r\n toIndex >= flat.length\r\n ) {\r\n return columns; // invalid indices\r\n }\r\n\r\n const fromInfo = this.findColumnByIndex(columns, fromIndex, flat);\r\n const toInfo = this.findColumnByIndex(columns, toIndex, flat);\r\n\r\n if (!fromInfo || !toInfo) return columns;\r\n\r\n const fromCol = fromInfo.column;\r\n const toCol = toInfo.column;\r\n\r\n let updatedCols = this.removeAtPath(columns, fromInfo.path);\r\n\r\n // Case 1: Same parent\r\n if (\r\n JSON.stringify(fromInfo.path.slice(0, -1)) ===\r\n JSON.stringify(toInfo.path.slice(0, -1))\r\n ) {\r\n return this.insertAtPath(updatedCols, toInfo.path, fromCol);\r\n }\r\n\r\n // Case 2: Drop target is a group header\r\n if (Array.isArray(toCol?.children)) {\r\n const cloned = this.deepClone(updatedCols);\r\n let parent = cloned;\r\n\r\n for (let i = 0; i < toInfo.path.length - 1; i++) {\r\n parent = parent[toInfo.path[i]].children;\r\n }\r\n\r\n const target = parent[toInfo.path[toInfo.path.length - 1]];\r\n if (!target.children) {\r\n target.children = [];\r\n }\r\n\r\n target.children.push(fromCol);\r\n return cloned;\r\n }\r\n\r\n // Case 3: Drop target is a regular column → wrap both into a group\r\n const groupHeader = {\r\n headerName: toCol.headerName || 'Group',\r\n children: [toCol, fromCol],\r\n };\r\n\r\n // Remove target from its original location\r\n updatedCols = this.removeAtPath(updatedCols, toInfo.path);\r\n\r\n // Insert group at target index\r\n return this.insertAtPath(updatedCols, toInfo.path, groupHeader);\r\n }\r\n}\r\n","import { Injectable } from '@angular/core';\r\n\r\n@Injectable({\r\n providedIn: 'root'\r\n})\r\nexport class CopyServiceService {\r\n\r\n constructor() { }\r\n getSelectedDataForCopy(\r\n dataSet: any[],\r\n columns: any[],\r\n rowSelectedIndexes: Set<number>,\r\n selectedCells: any[],\r\n getNestedValue: (obj: any, path: string) => any\r\n ): any[][] {\r\n const copiedRows: any[][] = [];\r\n const findRowByVirtualIndex = (vIndex: number) =>\r\n dataSet.find((r: any) => r.__virtualIndex === vIndex);\r\n if (rowSelectedIndexes && rowSelectedIndexes.size > 0) {\r\n const sortedIndexes = [...rowSelectedIndexes].sort((a, b) => a - b);\r\n for (const vIndex of sortedIndexes) {\r\n const row = findRowByVirtualIndex(vIndex);\r\n if (!row) continue;\r\n const extractValue = (field: string): string => {\r\n const nested = getNestedValue(row, field);\r\n const value =\r\n nested?.value ??\r\n nested?.name ??\r\n nested ??\r\n '-';\r\n\r\n if (Array.isArray(value)) {\r\n return value\r\n .map(v =>\r\n typeof v === 'object'\r\n ? (v.departmentName ?? v.roleName ?? '')\r\n : (v?.toString() ?? '')\r\n )\r\n .join(', ');\r\n }\r\n return value ?? '';\r\n };\r\n const mapped = columns.map(col => {\r\n if (col.children && Array.isArray(col.children)) {\r\n return col.children.map((c: { field: string }) => extractValue(c.field));\r\n }\r\n return [extractValue(col.field)];\r\n });\r\n const rowValues = mapped.reduce<string[]>((acc, curr) => acc.concat(curr), []);\r\n copiedRows.push(rowValues);\r\n }\r\n }\r\n if (selectedCells && selectedCells.length > 0) {\r\n const rowsMap = new Map<number, any[]>();\r\n for (const cell of selectedCells) {\r\n const row = findRowByVirtualIndex(cell.rowIndex);\r\n if (!row) continue;\r\n const col = columns[cell.colIndex];\r\n let fieldName = col?.field;\r\n if (col?.children && col.children[cell.subColIndex]) {\r\n fieldName = col.children[cell.subColIndex].field;\r\n }\r\n let value = getNestedValue(row, fieldName) || getNestedValue(row, fieldName)?.name || getNestedValue(row, fieldName) || '-';\r\n if (Array.isArray(value)) {\r\n value = value\r\n .map(v =>\r\n typeof v === 'object'\r\n ? v.departmentName ?? v.roleName ?? ''\r\n : v?.toString() ?? ''\r\n )\r\n .join(', ');\r\n } else {\r\n value = value ?? '';\r\n }\r\n if (!rowsMap.has(cell.rowIndex)) rowsMap.set(cell.rowIndex, []);\r\n rowsMap.get(cell.rowIndex)!.push(value);\r\n }\r\n const sortedCells = [...rowsMap.entries()]\r\n .sort(([a], [b]) => a - b)\r\n .map(([_, v]) => v);\r\n copiedRows.push(...sortedCells);\r\n }\r\n if (copiedRows.length === 0) {\r\n const activeCell = document.querySelector('.active-cell');\r\n if (activeCell) return [[activeCell.textContent?.trim() || '']];\r\n }\r\n\r\n const maxCols = copiedRows.reduce((max, row) => Math.max(max, row.length), 0);\r\n return copiedRows.map(row => {\r\n const newRow = [...row];\r\n while (newRow.length < maxCols) newRow.push('');\r\n return newRow;\r\n });\r\n }\r\n\r\n\r\n copyToClipboard(\r\n selectedData: any[][],\r\n selector = '.selected-cell, .active-cell, .row-selected'\r\n ): void {\r\n if (!selectedData || selectedData.length === 0) return;\r\n const textToCopy = selectedData\r\n .map(row => row.map(cell => (cell ?? '').toString()).join('\\t'))\r\n .join('\\n');\r\n navigator.clipboard.writeText(textToCopy).catch(err => console.error(err));\r\n const selectedEls = document.querySelectorAll(selector);\r\n selectedEls.forEach(el => el.classList.add('flash-bg'));\r\n setTimeout(() => {\r\n selectedEls.forEach(el => el.classList.remove('flash-bg'));\r\n }, 1000);\r\n }\r\n\r\n\r\n updateRows: any[] = [];\r\n async pasteFromClipboardText(\r\n text: string,\r\n dataSet: any[],\r\n columns: any[],\r\n startRowIndex: number,\r\n startColIndex: number,\r\n startSubColIndex: number = 0\r\n ): Promise<any[]> {\r\n if (!text) return [];\r\n this.updateRows = [];\r\n const rows = text.split(/\\r?\\n/).map(r => r.split('\\t'));\r\n const flattenedColumns: any[] = [];\r\n columns.forEach(col => {\r\n if (col.children?.length) {\r\n col.children.forEach((child: any) => flattenedColumns.push({ parent: col, ...child }));\r\n } else {\r\n flattenedColumns.push(col);\r\n }\r\n });\r\n let startFlattenedIndex = 0;\r\n for (let i = 0; i < startColIndex; i++) {\r\n const col = columns[i];\r\n startFlattenedIndex += col.children?.length || 1;\r\n }\r\n const startCol = columns[startColIndex];\r\n if (startCol?.children?.length) {\r\n startFlattenedIndex += Math.min(startSubColIndex, startCol.children.length - 1);\r\n }\r\n\r\n rows.forEach((rowValues, rOffset) => {\r\n const targetRowIndex = startRowIndex + rOffset;\r\n const row = dataSet.find(r => r?.__virtualIndex === targetRowIndex);\r\n if (!row) return;\r\n\r\n rowValues.forEach((value, cOffset) => {\r\n const targetIndex = startFlattenedIndex + cOffset;\r\n if (targetIndex < 0 || targetIndex >= flattenedColumns.length) return;\r\n\r\n const targetColumn = flattenedColumns[targetIndex];\r\n this.setNestedValue(row, targetColumn, value);\r\n });\r\n });\r\n\r\n return this.updateRows;\r\n }\r\n\r\n\r\n setNestedValue(obj: any, column: any, option: any, calledFromInput = false): void {\r\n debugger\r\n if (column.type === 'date' || column.type === 'image') return;\r\n const path = column.field;\r\n if (!path) return;\r\n const keys = path.split('.');\r\n const lastKey = keys.pop();\r\n const parent = keys.reduce((acc: any, key: string) => (acc[key] ??= {}), obj);\r\n if (parent && lastKey) {\r\n if (typeof option === 'object' && option !== null) {\r\n parent[lastKey] = {\r\n id: option.id ?? 1,\r\n value: option.value ?? option,\r\n };\r\n } else {\r\n parent[lastKey] = option;\r\n }\r\n }\r\n const sendObj = {\r\n data: { ...obj },\r\n eventType: 'onCellEdit',\r\n };\r\n this.updateRows.push(obj);\r\n }\r\n\r\n\r\n cutSelectedSelectedData(\r\n dataSet: any[],\r\n rowSelectedIndexes: Set<number>,\r\n selectedCells: any[],\r\n columns: any[],\r\n setNestedValue: (obj: any, column: any, value: any) => void\r\n ): any {\r\n let updatedData = [...dataSet];\r\n if (rowSelectedIndexes?.size) {\r\n const indexes = [...rowSelectedIndexes];\r\n updatedData = updatedData.filter(\r\n (row: any) => !indexes.includes(row.__virtualIndex)\r\n );\r\n // rowSelectedIndexes.clear();\r\n }\r\n else if (selectedCells?.length) {\r\n this.updateRows = [];\r\n for (const cell of selectedCells) {\r\n const row = updatedData.find(r => r.__virtualIndex === cell.rowIndex);\r\n if (!row) continue;\r\n let col = columns[cell.colIndex];\r\n if (col?.children && col.children[cell.subColIndex]) {\r\n col = col.children[cell.subColIndex];\r\n }\r\n this.setNestedValue(row, col, '');\r\n }\r\n // selectedCells.length = 0;\r\n }\r\n return {updatedData: updatedData, updatedRows: this.updateRows};\r\n }\r\n\r\n\r\n cutWithAnimation(\r\n selectedData: any[][],\r\n selector = '.selected-cell, .active-cell, .row-selected'\r\n ): void {\r\n if (!selectedData || selectedData.length === 0) return;\r\n\r\n const textToCopy = selectedData\r\n .map(row => row.map(cell => (cell ?? '').toString()).join('\\t'))\r\n .join('\\n');\r\n\r\n navigator.clipboard.writeText(textToCopy).catch(err => console.error(err));\r\n\r\n // Apply red cut animation\r\n const selectedEls = document.querySelectorAll(selector);\r\n selectedEls.forEach(el => el.classList.add('cut-flash-bg'));\r\n setTimeout(() => {\r\n selectedEls.forEach(el => el.classList.remove('cut-flash-bg'));\r\n }, 1000);\r\n }\r\n\r\n\r\n}\r\n","import { Pipe, PipeTransform } from '@angular/core';\r\n\r\n@Pipe({\r\n name: 'filter',\r\n})\r\nexport class FilterPipe implements PipeTransform {\r\ntransform(items: any[], searchText: string, key?: string): any[] {\r\n if (!items || !searchText) return items;\r\n\r\n searchText = searchText.toLowerCase();\r\n\r\nreturn items.filter(item => {\r\n const value = key ? item?.[key] : item;\r\n return value?.toString().toLowerCase().includes(searchText);\r\n });\r\n}}\r\n","import {\r\n Component,\r\n OnInit,\r\n Input,\r\n Output,\r\n OnChanges,\r\n SimpleChanges,\r\n ElementRef,\r\n ViewChild,\r\n AfterViewInit,\r\n HostListener,\r\n ChangeDetectorRef,\r\n EventEmitter,\r\n NgZone,\r\n ChangeDetectionStrategy,\r\n} from '@angular/core';\r\nimport { SplitColumnsService } from '../services/split-columns.service';\r\nimport { CommonService } from '../services/common.service';\r\nimport { SwapColumnsService } from '../services/swap-columns.service';\r\nimport {\r\n CdkDrag,\r\n CdkDragDrop,\r\n CdkDragEnd,\r\n CdkDragEnter,\r\n CdkDragExit,\r\n CdkDragMove,\r\n CdkDragSortEvent,\r\n CdkDragStart,\r\n DragDrop,\r\n moveItemInArray,\r\n} from '@angular/cdk/drag-drop';\r\nimport {\r\n trigger,\r\n state,\r\n style,\r\n transition,\r\n animate,\r\n} from '@angular/animations';\r\nimport { CdkDropList } from '@angular/cdk/drag-drop';\r\nimport { STATUSES_BADGE_MAP } from './statuses';\r\nimport { CopyServiceService } from '../services/copy-service.service';\r\n\r\ninterface CellPosition {\r\n rowIndex: number;\r\n colIndex: number;\r\n subColIndex: number;\r\n field: string;\r\n key: string;\r\n}\r\n\r\n\r\n@Component({\r\n selector: 'data-grid',\r\n templateUrl: './data-grid.component.html',\r\n styleUrls: ['./data-grid.component.scss', '../css/bootstrap.css'],\r\n animations: [\r\n trigger('accordionToggle', [\r\n state(\r\n 'collapsed',\r\n style({ height: '0px', overflow: 'unset' })\r\n ),\r\n state('expanded', style({ height: '*', overflow: 'unset' })),\r\n transition('collapsed <=> expanded', animate('300ms ease-in')),\r\n ]),\r\n trigger('slideToggle', [\r\n transition(':enter', [\r\n style({ height: '0px', opacity: 0, overflow: 'hidden' }),\r\n animate('300ms ease-out', style({ height: '*', opacity: 1 }))\r\n ]),\r\n transition(':leave', [\r\n style({ height: '*', opacity: 1, overflow: 'hidden' }),\r\n animate('300ms ease-in', style({ height: '0px', opacity: 0 }))\r\n ])\r\n ]),\r\n trigger('slideUp', [\r\n state('void', style({ transform: 'translateY(100%)', opacity: 0 })),\r\n state('*', style({ transform: 'translateY(0)', opacity: 1 })),\r\n transition('void => *', animate('300ms ease-out')),\r\n transition('* => void', animate('300ms ease-in')),\r\n ])\r\n ],\r\n})\r\nexport class DataGridComponent implements OnChanges, AfterViewInit {\r\n // **************************************//\r\n // **************************************//\r\n // ********** Input Goes Here *********** //\r\n // **************************************//\r\n // **************************************//\r\n\r\n // Pagination Config Store Here\r\n @Input() paginationConfig: any = {};\r\n\r\n // The dataset store here;\r\n @Input() dataSet: any[] = [];\r\n\r\n // The columns Store Here\r\n @Input() columns: any[] = [];\r\n\r\n // Row Height;\r\n @Input() rowHeight: number = 44;\r\n\r\n // Header Row Height;\r\n @Input() headerRowHeight: number = 44;\r\n\r\n // Show Vertical Borders;\r\n @Input() showVerticalBorder: boolean = true;\r\n\r\n // Even Rows Background Color;\r\n @Input() evenRowsBackgroundColor: string | undefined = undefined;\r\n\r\n // Even Rows Background Color;\r\n @Input() oddRowsBackgroundColor: string | undefined = undefined;\r\n\r\n // Header Rows Background Color;\r\n @Input() headerBackgroundColor: string = '#eaeaea';\r\n\r\n // Header Rows Background Color;\r\n @Input() checkboxesBackgroundColor: string = '#eaeaea';\r\n\r\n // Show Columns Grouping;\r\n @Input() showColumnsGrouping: boolean = false;\r\n\r\n // Row Hovered Background color;\r\n @Input() rowHoverColor: string | undefined = 'rgba(0, 123, 255, 0.1)';\r\n\r\n // Left PinnedBackground color;\r\n @Input() leftPinnedBackgroundColor: string | undefined = '#000';\r\n\r\n // Body Background color;\r\n @Input() bodyBackgroundColor: string | undefined = '#fff';\r\n\r\n // Right Pinned Background color;\r\n @Input() rightPinnedBackgroundColor: string | undefined = '#000';\r\n\r\n // Side Menu Background color;\r\n @Input() sidemenuBackgroundColor: string | undefined = '#000';\r\n\r\n // Body text color;\r\n @Input() bodyTextColor: string | undefined = '#000';\r\n\r\n // Header text color;\r\n @Input() headerTextColor: string | undefined = '#000';\r\n\r\n // Header text size;\r\n @Input() headerTextFontsSize: number | undefined = 16;\r\n\r\n // Body text color;\r\n @Input() bodyTextFontsSize: number | undefined = 16;\r\n\r\n // Header font weight;\r\n @Input() headerFontWeight: number | undefined = 500;\r\n\r\n // Body Font Weight;\r\n @Input() bodyFontWeight: number | undefined = 400;\r\n\r\n // Checked Row Background Color;\r\n @Input() checkedRowBackgroundColor: string | undefined = '';\r\n\r\n // dropdowns Background Color;\r\n @Input() dropdownsBackgroundColor: string | undefined = '';\r\n\r\n // Footer row Height;\r\n @Input() footerRowHeight: number = 46;\r\n\r\n // Footer row Height;\r\n @Input() topGroupedBadgesBackgroundColor: string | undefined = '';\r\n\r\n // Show Row wise grouping;\r\n @Input() showRowsGrouping: boolean | undefined = false;\r\n\r\n // Show Row wise grouping;\r\n @Input() showFilterRow: boolean | undefined = false;\r\n\r\n // Show Row wise grouping;\r\n @Input() fontFaimly: string | undefined = 'sans-serif';\r\n\r\n // Show SideColumn;\r\n @Input() showSideMenu: boolean = false;\r\n\r\n // Footer Padding\r\n @Input() footerPadding: number = 3\r\n\r\n\r\n // Footer Padding\r\n @Input() topFilterRowHeight: number = 50;\r\n\r\n // Show Rows shading\r\n @Input() rowShadingEnabled: boolean = false;\r\n\r\n // Show Rows shading\r\n @Input() showSerialNumber: boolean = false;\r\n\r\n // Single Spa Url Attach to icons\r\n @Input() singleSpaAssetsPath: string = 'assets/';\r\n\r\n\r\n // Applied Filters\r\n @Input() filtersConfig: any[] = [];\r\n\r\n\r\n // Applied Filters\r\n @Input() loading: boolean = false;\r\n\r\n //Vertical Scrollbar style\r\n @Input() verticalScrollbarWidth: 'auto' | 'thin' = 'auto';\r\n\r\n //Horizintal Scrollbar style\r\n @Input() horizintalScrollbarWidth: 'auto' | 'thin' = 'auto';\r\n\r\n // Show Cell details box\r\n @Input() showCellDetailsBox: boolean = false;\r\n\r\n //Date format\r\n\r\n @Input() dateFormat: string = 'dd/MM/yyyy'\r\n\r\n //Date format\r\n @Input() tableSearch: string = '';\r\n\r\n\r\n //Date format\r\n @Input() actions: any[] = ['edit', 'delete'];\r\n\r\n\r\n // Table Config for paginations is here\r\n @Input() config: any;\r\n\r\n\r\n // Selection task bar\r\n @Input() showTaskbar = false\r\n\r\n // Table Name for state manage\r\n @Input() tableName = true\r\n\r\n\r\n // Listing type\r\n @Input() listingType: string | boolean = '';\r\n\r\n // Listing type\r\n\r\n @Input() checkboxState: { reset: boolean } = {\r\n reset: true\r\n }\r\n\r\n // Taskbar actions\r\n @Input() taskbarActions: any[] = []\r\n\r\n\r\n // Sorting Config to show sort icons\r\n @Input() sortingConfig: { field: string, order_by: string } | null = null;\r\n\r\n\r\n @Input() tableFilterViewId: any = '';\r\n\r\n\r\n @Input() selectedTableLayout: any = 'medium'\r\n\r\n @Input() closeDropdown: { preset: { closed: boolean, loading: boolean } } = { preset: { closed: false, loading: false } };\r\n\r\n // Table View\r\n\r\n // GlobalSearch\r\n @Input() globalSearchText: string = '';\r\n\r\n// Nested Table Row Fontsize\r\n @Input() nestedTablerowFontsize = 14;\r\n\r\n // Nested table row Header row Height\r\n @Input() nestedTableHeaderRowHeight = 40;\r\n\r\n // Nested Table row height\r\n @Input() nestedTablerowHeight = 36;\r\n\r\n\r\n @Input() gridType: string = 'Assets';\r\n\r\n\r\n @Input() leftPinnedBoxshadow: string = '#4b4b4b 1px 1px 5px 0px';\r\n\r\n @Input() rightPinnedBoxshadow: string = '#4b4b4b 4px 1px 6px 0px';\r\n\r\n // GlobalSearch\r\n @Input() selectedRowsBackgroundColor: string = '#8ac5ff';\r\n\r\n // GlobalSearch\r\n @Input() nestedTableHeaderBAckgroundColor: string = '#eaeaea';\r\n\r\n @Input() tableView: any[] = [];\r\n\r\n\r\n @Input() columnThreedotsMunuConfig = {\r\n showPinleft: true,\r\n showPinright: true,\r\n showAscending: true,\r\n showDescending: true,\r\n showFilter: true,\r\n showRowsGrouping: this.showRowsGrouping,\r\n showAutosizeAllColumns: false,\r\n showAutosizeThisColumn: false,\r\n showChoseColumns: false,\r\n showResetColumns: false,\r\n\r\n\r\n };\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n // ///////////////////////////////////////////////////////////////////////////////////////////\r\n // ///////////////////////////////////////////////////////////////////////////////////////////\r\n // Out Put Events\r\n // ///////////////////////////////////////////////////////////////////////////////////////////\r\n // ///////////////////////////////////////////////////////////////////////////////////////////\r\n\r\n\r\n\r\n //Change Table Layout\r\n @Output() public changeLayout = new EventEmitter<any>();\r\n\r\n // Filter Apply event;\r\n @Output() public filterOptions = new EventEmitter<any>();\r\n\r\n @Output() genericEvent: EventEmitter<any> = new EventEmitter();\r\n\r\n @Output() tablePresetConfig: EventEmitter<any> = new EventEmitter();\r\n\r\n @Output() sortingOrderOptions: EventEmitter<any> = new EventEmitter<any>();\r\n\r\n @Output() createUpdateConfigListing: EventEmitter<any> = new EventEmitter<any>();\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n groupedColumns: any[] = [];\r\n activeCol: any = null;\r\n activeFilterCell: any = null;\r\n showActionsDropDown = false;\r\n sideMenuVisible = false;\r\n pivotMode: boolean = false;\r\n columnSearch: string = '';\r\n expandAllAccordians = true;\r\n currentOpenedSideMenue: 'cols' | 'filtrs' | null = null;\r\n originalColumns: any[] = [];\r\n originalDataSet: any[] = [];\r\n activeTopButton: string | null = '';\r\n filterColumnsList: any[] = [];\r\n groupBoxPadding = 200;\r\n presetName: string = '';\r\n presetFilter: any = '';\r\n searchTextPresetTable = '';\r\n addFilterColumnInput = '';\r\n searchInDropdown = '';\r\n addFilterDropdownSearch = ''\r\n topShowHideColumns = '';\r\n choseColumnsSearch = ''\r\n editinDropdownSearch = '';\r\n isThreeDotsFilterOpen = false;\r\n confirmDelete = false;\r\n fontFamilies: string[] = [\r\n // Common Excel fonts\r\n 'Arial',\r\n 'Arial Black',\r\n 'Bahnschrift',\r\n 'Calibri',\r\n 'Cambria',\r\n 'Cambria Math',\r\n 'Candara',\r\n 'Comic Sans MS',\r\n 'Consolas',\r\n 'Constantia',\r\n 'Corbel',\r\n 'Courier New',\r\n 'Ebrima',\r\n 'Franklin Gothic Medium',\r\n 'Gabriola',\r\n 'Gadugi',\r\n 'Georgia',\r\n 'Impact',\r\n 'Ink Free',\r\n 'Javanese Text',\r\n 'Leelawadee UI',\r\n 'Lucida Console',\r\n 'Lucida Sans Unicode',\r\n 'Malgun Gothic',\r\n 'Microsoft Himalaya',\r\n 'Microsoft JhengHei',\r\n 'Microsoft New Tai Lue',\r\n 'Microsoft PhagsPa',\r\n 'Microsoft Sans Serif',\r\n 'Microsoft Tai Le',\r\n 'Microsoft YaHei',\r\n 'Microsoft Yi Baiti',\r\n 'Mongolian Baiti',\r\n 'MS Gothic',\r\n 'MS PGothic',\r\n 'MS UI Gothic',\r\n 'MV Boli',\r\n 'Nirmala UI',\r\n 'Palatino Linotype',\r\n 'Segoe Print',\r\n 'Segoe Script',\r\n 'Segoe UI',\r\n 'Segoe UI Historic',\r\n 'Segoe UI Emoji',\r\n 'Segoe UI Symbol',\r\n 'SimSun',\r\n 'Sitka Small',\r\n 'Sylfaen',\r\n 'Symbol',\r\n 'Tahoma',\r\n 'Times New Roman',\r\n 'Trebuchet MS',\r\n 'Verdana',\r\n 'Webdings',\r\n 'Wingdings',\r\n 'Yu Gothic',\r\n 'sans-serif',\r\n 'serif',\r\n 'monospace'\r\n ];\r\n\r\n fontSizes: string[] = [\r\n '8', '9', '10', '11', '12', '14', '16', '18', '20', '24', '28', '32'\r\n ];\r\n\r\n\r\n\r\n\r\n constructor(\r\n private columnService: SplitColumnsService,\r\n private cdr: ChangeDetectorRef,\r\n public commonSevice: CommonService,\r\n private swapColumnService: SwapColumnsService,\r\n private elementRef: ElementRef,\r\n private ngZone: NgZone,\r\n private copyService: CopyServiceService\r\n ) { }\r\n\r\n @ViewChild('cellText', { static: false }) cellText!: ElementRef;\r\n async ngAfterViewInit() {\r\n setTimeout(async () => {\r\n // this.updateFlattenedData();\r\n // this.computeViewportRows();\r\n // this.updateVisibleRows(0);\r\n // this.cdr.detectChanges();\r\n await this.SetColumnsDefaultWidth();\r\n await this.refreshHeaders()\r\n this.updateFlattenedData();\r\n this.computeViewportRows();\r\n this.updateVisibleRows(0);\r\n if (this.cellText) {\r\n const observer = new ResizeObserver(() => {\r\n this.cdr.detectChanges();\r\n });\r\n observer.observe(this.cellText.nativeElement);\r\n }\r\n this.cdr.detectChanges();\r\n }, 300);\r\n\r\n\r\n }\r\n\r\n async ngOnChanges(changes: SimpleChanges) {\r\n if (changes['filtersConfig']) {\r\n await this.applyFilteroptionList();\r\n this.checkFilterChangesEffect();\r\n }\r\n if (changes['columns']?.currentValue?.length) {\r\n // this.columns = this.columnService.setColumnsQuery(this.columns);\r\n this.originalColumns = structuredClone(this.columns);\r\n if (this.dataGridContainer?.nativeElement?.offsetWidth) {\r\n await this.SetColumnsDefaultWidth();\r\n await this.updateColumnWidthsAndGroups();\r\n await this.refreshPreviewColumns();\r\n this.setSectionsWidth();\r\n this.cdr.detectChanges();\r\n }\r\n this.generateDropListIds();\r\n this.cdr.detectChanges();\r\n\r\n requestAnimationFrame(() => {\r\n\r\n this.cdr.detectChanges();\r\n });\r\n }\r\n if (changes['dataSet']) {\r\n this.dataSet = this.dataSet?.map((row, i) => ({\r\n ...row,\r\n __virtualIndex: i + 1\r\n }));\r\n this.originalDataSet = structuredClone(this.dataSet);\r\n this.expandedCells.clear();\r\n this.updateFlattenedData();\r\n this.cdr.detectChanges();\r\n setTimeout(() => {\r\n if (this.mainScroll?.nativeElement) {\r\n this.computeViewportRows();\r\n this.updateVisibleRows(0);\r\n }\r\n }, 100);\r\n if (this.centerPinnedHeader?.nativeElement) {\r\n void this.centerPinnedHeader.nativeElement.offsetWidth;\r\n if(this.centerScrollableBody?.nativeElement){\r\n this.centerScrollableBody.nativeElement.scrollLeft = this.centerPinnedHeader?.nativeElement?.scrollLeft;\r\n }\r\n this.cdr.detectChanges();\r\n }\r\n\r\n }\r\n if (changes['checkboxState']?.currentValue?.reset) {\r\n this.selectedRows.clear();\r\n this.cdr.detectChanges();\r\n }\r\n if (changes['closeDropdown']) {\r\n if (this.closeDropdown.preset.closed) {\r\n this.activeTopButton = null;\r\n }\r\n }\r\n if (changes['oddRowsBackgroundColor']) {\r\n this.rowShadingEnabled = this.oddRowsBackgroundColor ? this.rowShadingEnabled = true : false;\r\n }\r\n }\r\n\r\n\r\n\r\n async applyFilteroptionList() {\r\n debugger\r\n const updatedColumns = await this.commonSevice.applyFiltersToColumns(this.columns, this.filtersConfig);\r\n this.columns = [...updatedColumns];\r\n this.cdr.detectChanges();\r\n }\r\n leftPinnedColumns: any[] = [];\r\n centerColumns: any[] = [];\r\n rightPinnedColumns: any[] = [];\r\n\r\n previewLeftPinnedColumns: any[] = [];\r\n previewCenterColumns: any[] = [];\r\n previewRightPinnedColumns: any[] = [];\r\n\r\n // Main Container Template References\r\n @ViewChild('dataGridContainer')\r\n dataGridContainer!: ElementRef<HTMLDivElement>;\r\n\r\n // Body Template References\r\n @ViewChild('leftPinnedBody')\r\n leftPinnedBody!: ElementRef<HTMLDivElement>;\r\n @ViewChild('centerPinnedBody')\r\n centerPinnedBody!: ElementRef<HTMLDivElement>;\r\n @ViewChild('rightPinnedBody')\r\n rightPinnedBody!: ElementRef<HTMLDivElement>;\r\n\r\n // Headers Template References\r\n @ViewChild('leftPinnedHeader')\r\n leftPinnedHeader!: ElementRef<HTMLDivElement>;\r\n @ViewChild('centerPinnedHeader')\r\n centerPinnedHeader!: ElementRef<HTMLDivElement>;\r\n @ViewChild('rightPinnedHeader')\r\n rightPinnedHeader!: ElementRef<HTMLDivElement>;\r\n\r\n @ViewChild('columnsGroupedBox')\r\n columnsGroupedBox!: ElementRef<HTMLDivElement>;\r\n\r\n // Center Fake scrollbard\r\n @ViewChild('centerFakeScrollbar')\r\n centerFakeScrollbar!: ElementRef<HTMLDivElement>;\r\n async updateColumnWidthsAndGroups(columns: any = null) {\r\n if (!this.dataGridContainer) return;\r\n\r\n const containerWidth = this.dataGridContainer.nativeElement.offsetWidth;\r\n\r\n // Wrap in a promise so we can await\r\n const { left, center, right }: any = await new Promise(resolve => {\r\n const prepared = this.columnService.prepareColumns(\r\n columns ? columns : this.columns,\r\n containerWidth\r\n );\r\n resolve(prepared);\r\n });\r\n\r\n this.leftPinnedColumns = left;\r\n this.centerColumns = center;\r\n this.rightPinnedColumns = right;\r\n await new Promise(r => setTimeout(r, 0));\r\n this.cdr.detectChanges();\r\n }\r\n\r\n async refreshPreviewColumns(columns: any = null) {\r\n if (!this.dataGridContainer) return;\r\n\r\n const containerWidth = this.dataGridContainer.nativeElement.offsetWidth;\r\n\r\n // Wrap prepareColumns in a Promise to make it awaitable\r\n const { left, center, right }: any = await new Promise(resolve => {\r\n const prepared = this.columnService.prepareColumns(\r\n columns ? columns : this.columns,\r\n containerWidth\r\n );\r\n resolve(prepared);\r\n });\r\n\r\n this.previewLeftPinnedColumns = left;\r\n this.previewCenterColumns = center;\r\n this.previewRightPinnedColumns = right;\r\n await new Promise(r => setTimeout(r, 10));\r\n this.cdr.detectChanges();\r\n }\r\n\r\n\r\n async SetColumnsDefaultWidth() {\r\n const containerWidth = this.dataGridContainer?.nativeElement.offsetWidth;\r\n this.columns = this.columnService.assignDefaultWidths(\r\n this.columns,\r\n containerWidth\r\n );\r\n await new Promise(resolve => setTimeout(resolve, 0));\r\n this.cdr.detectChanges();\r\n }\r\n\r\n\r\n setSectionsWidth() {\r\n const left = document.querySelector('.left-pinned-body') as HTMLElement;\r\n const center = document.querySelector(\r\n '.center-scrollable-body'\r\n ) as HTMLElement;\r\n const right = document.querySelector('.right-pinned-body') as HTMLElement;\r\n if (left) {\r\n left.style.minWidth = `${this.leftPinnedHeader.nativeElement.offsetWidth}`;\r\n }\r\n if (center) {\r\n left.style.minWidth = `${this.centerPinnedHeader.nativeElement.offsetWidth}`;\r\n }\r\n if (right) {\r\n left.style.minWidth = `${this.rightPinnedHeader.nativeElement.offsetWidth}`;\r\n }\r\n }\r\n\r\n // onCenterBodyScroll(): void {\r\n // const scrollTop = this.centerPinnedBody.nativeElement.scrollTop;\r\n // const scrollLeft = this.centerPinnedBody.nativeElement.scrollLeft;\r\n\r\n // // Sync vertical scroll with left & right pinned bodies\r\n // this.leftPinnedBody.nativeElement.scrollTop = scrollTop;\r\n // this.rightPinnedBody.nativeElement.scrollTop = scrollTop;\r\n\r\n // // Sync horizontal scroll with center header\r\n // this.centerPinnedHeader.nativeElement.scrollLeft = scrollLeft;\r\n // this.centerFakeScrollbar.nativeElement.scrollLeft = scrollLeft;\r\n // }\r\n\r\n onLeftBodyScroll(): void {\r\n const scrollTop = this.leftPinnedBody.nativeElement.scrollTop;\r\n this.centerPinnedBody.nativeElement.scrollTop = scrollTop;\r\n this.rightPinnedBody.nativeElement.scrollTop = scrollTop;\r\n }\r\n onRightBodyScroll(): void {\r\n const scrollTop = this.rightPinnedBody.nativeElement.scrollTop;\r\n this.centerPinnedBody.nativeElement.scrollTop = scrollTop;\r\n this.leftPinnedBody.nativeElement.scrollTop = scrollTop;\r\n }\r\n\r\n fakeScrollbarScrollLeft = 0;\r\n onFakeScroll(event: Event) {\r\n const target = event.target as HTMLElement;\r\n this.fakeScrollbarScrollLeft = target.scrollLeft;\r\n this.centerPinnedBody.nativeElement.scrollLeft = target.scrollLeft;\r\n this.centerPinnedHeader.nativeElement.scrollLeft = target.scrollLeft;\r\n }\r\n\r\n\r\n\r\n\r\n getNestedValue(obj: any, field: string): any {\r\n return field.split('.').reduce((acc, part) => acc && acc[part], obj);\r\n }\r\n\r\n isNestedValueArray(obj: any, field: string): boolean {\r\n if (!obj || !field) return false;\r\n const value = field\r\n .split('.')\r\n .reduce((acc, part) => (acc && acc[part] !== undefined ? acc[part] : undefined), obj);\r\n return Array.isArray(value);\r\n }\r\n\r\n onResizeGroup(event: MouseEvent, col: any, isRightPinned?: boolean): void {\r\n event.preventDefault();\r\n event.stopPropagation();\r\n\r\n const startX = event.clientX;\r\n const children = col.children || [];\r\n if (!children.length) return;\r\n\r\n const childWidths: { field: string; width: number }[] = children.map(\r\n (child: any) => {\r\n con