@toolbox-web/grid 2.0.0-alpha → 2.0.0-alpha.1
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/all.js +2 -2
- package/all.js.map +1 -1
- package/index.js +1 -1
- package/index.js.map +1 -1
- package/lib/plugins/export/excel-styles.d.ts +45 -0
- package/lib/plugins/export/index.d.ts +1 -1
- package/lib/plugins/export/index.js +1 -1
- package/lib/plugins/export/index.js.map +1 -1
- package/lib/plugins/export/types.d.ts +68 -0
- package/lib/plugins/grouping-rows/GroupingRowsPlugin.d.ts +91 -2
- package/lib/plugins/grouping-rows/grouping-rows.d.ts +29 -1
- package/lib/plugins/grouping-rows/index.d.ts +1 -1
- package/lib/plugins/grouping-rows/index.js +2 -2
- package/lib/plugins/grouping-rows/index.js.map +1 -1
- package/lib/plugins/grouping-rows/types.d.ts +104 -0
- package/lib/plugins/server-side/index.js +1 -1
- package/lib/plugins/server-side/index.js.map +1 -1
- package/package.json +1 -1
- package/umd/grid.all.umd.js +1 -1
- package/umd/grid.all.umd.js.map +1 -1
- package/umd/plugins/export.umd.js +1 -1
- package/umd/plugins/export.umd.js.map +1 -1
- package/umd/plugins/grouping-rows.umd.js +1 -1
- package/umd/plugins/grouping-rows.umd.js.map +1 -1
- package/umd/plugins/server-side.umd.js +1 -1
- package/umd/plugins/server-side.umd.js.map +1 -1
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("../../core/plugin/base-plugin")):"function"==typeof define&&define.amd?define(["exports","../../core/plugin/base-plugin"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).TbwGridPlugin_export={},e.TbwGrid)}(this,function(e,t){"use strict";function n(e,t){return t?.length?[...t].sort((e,t)=>e-t).map(t=>e[t]).filter(e=>null!=e):e}function o(e,t=!0){if(null==e)return"";if(e instanceof Date)return e.toISOString();if("object"==typeof e)return JSON.stringify(e);const n=String(e);return t&&(n.includes(",")||n.includes('"')||n.includes("\n")||n.includes("\r"))?`"${n.replace(/"/g,'""')}"`:n}function
|
|
1
|
+
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("../../core/plugin/base-plugin")):"function"==typeof define&&define.amd?define(["exports","../../core/plugin/base-plugin"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).TbwGridPlugin_export={},e.TbwGrid)}(this,function(e,t){"use strict";function n(e,t){return t?.length?[...t].sort((e,t)=>e-t).map(t=>e[t]).filter(e=>null!=e):e}function o(e,t=!0){if(null==e)return"";if(e instanceof Date)return e.toISOString();if("object"==typeof e)return JSON.stringify(e);const n=String(e);return t&&(n.includes(",")||n.includes('"')||n.includes("\n")||n.includes("\r"))?`"${n.replace(/"/g,'""')}"`:n}function r(e,t){const n=URL.createObjectURL(e),o=document.createElement("a");o.href=n,o.download=t,o.style.display="none",document.body.appendChild(o),o.click(),document.body.removeChild(o),URL.revokeObjectURL(n)}function s(e){return JSON.stringify(l(e))}function l(e){if(null==e||"object"!=typeof e)return e;if(Array.isArray(e))return e.map(l);const t={};for(const n of Object.keys(e).sort())t[n]=l(e[n]);return t}class i{#e=new Map;#t=0;register(e){const t=s(e),n=this.#e.get(t);if(n)return n.id;this.#t++;const o=`s${this.#t}`;return this.#e.set(t,{id:o,style:e}),o}getStyleId(e){return this.#e.get(s(e))?.id}get size(){return this.#e.size}toXml(){if(0===this.#e.size)return"";let e="\n<Styles>";for(const{id:t,style:n}of this.#e.values())e+=c(t,n);return e+="\n</Styles>",e}}function c(e,t){let n=`\n<Style ss:ID="${e}">`;if(t.font&&(n+="<Font",t.font.name&&(n+=` ss:FontName="${t.font.name}"`),t.font.size&&(n+=` ss:Size="${t.font.size}"`),t.font.bold&&(n+=' ss:Bold="1"'),t.font.italic&&(n+=' ss:Italic="1"'),t.font.color&&(n+=` ss:Color="${t.font.color}"`),n+="/>"),t.fill){const e=t.fill.pattern??"Solid";n+=`<Interior ss:Color="${t.fill.color}" ss:Pattern="${e}"/>`}return t.numberFormat&&(n+=`<NumberFormat ss:Format="${t.numberFormat}"/>`),t.alignment&&(n+="<Alignment",t.alignment.horizontal&&(n+=` ss:Horizontal="${t.alignment.horizontal}"`),t.alignment.vertical&&(n+=` ss:Vertical="${t.alignment.vertical}"`),t.alignment.wrapText&&(n+=' ss:WrapText="1"'),n+="/>"),t.borders&&(n+="<Borders>",t.borders.top&&(n+=f("Top",t.borders.top)),t.borders.bottom&&(n+=f("Bottom",t.borders.bottom)),t.borders.left&&(n+=f("Left",t.borders.left)),t.borders.right&&(n+=f("Right",t.borders.right)),n+="</Borders>"),n+="</Style>",n}function f(e,t){let n=`<Border ss:Position="${e}" ss:LineStyle="Continuous" ss:Weight="${function(e){switch(e){case"Thin":return 1;case"Medium":return 2;case"Thick":return 3}}(t.style)}"`;return t.color&&(n+=` ss:Color="${t.color}"`),n+="/>",n}function a(e,t,n,o,r){if(t.cellStyle){const s=t.cellStyle(n,o,r);if(s)return e.register(s)}const s=t.columnStyles?.[o];return s?e.getStyleId(s):t.defaultStyle?e.getStyleId(t.defaultStyle):void 0}function u(e,t){const n=Math.min(t.length,50);let o=(e.header??e.field).length;for(let r=0;r<n;r++){const n=t[r][e.field],s=null==n?0:String(n).length;s>o&&(o=s)}return o+2}function d(e){return e.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'")}function p(e,t,n){const o=n.excelStyles,r=o?function(e){const t=new i;if(e.headerStyle&&t.register(e.headerStyle),e.defaultStyle&&t.register(e.defaultStyle),e.columnStyles)for(const n of Object.values(e.columnStyles))t.register(n);return t}(o):void 0;let s='<?xml version="1.0" encoding="UTF-8"?>\n<?mso-application progid="Excel.Sheet"?>\n<Workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet"\n xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet">';if(r){if(o.cellStyle)for(const n of e)for(const e of t){const t=n[e.field],s=o.cellStyle(t,e.field,n);s&&r.register(s)}s+=r.toXml()}s+='\n<Worksheet ss:Name="Sheet1">\n<Table>',o&&(s+=function(e,t,n){const o=n.columnWidths,r=n.autoFitColumns;if(!o&&!r)return"";let s="";for(const l of e){let e=o?.[l.field];null==e&&r&&(e=u(l,t)),s+=null!=e?`\n<Column ss:Width="${7*e}"/>`:"\n<Column/>"}return s}(t,e,o));const l=o?.headerStyle&&r?r.getStyleId(o.headerStyle):void 0;if(!1!==n.includeHeaders){s+="\n<Row>";for(const e of t){const t=e.header||e.field;s+=`<Cell${l?` ss:StyleID="${l}"`:""}><Data ss:Type="String">${d(n.processHeader?n.processHeader(t,e.field):t)}</Data></Cell>`}s+="</Row>"}for(const i of e){s+="\n<Row>";for(const e of t){let t=i[e.field];n.processCell&&(t=n.processCell(t,e.field,i));let l="String",c="";null==t?c="":"number"!=typeof t||isNaN(t)?t instanceof Date?(l="DateTime",c=t.toISOString()):c=d(String(t)):(l="Number",c=String(t));const f=r&&o?a(r,o,t,e.field,i):void 0;s+=`<Cell${f?` ss:StyleID="${f}"`:""}><Data ss:Type="${l}">${c}</Data></Cell>`}s+="</Row>"}return s+="\n</Table>\n</Worksheet>\n</Workbook>",s}class m extends t.BaseGridPlugin{name="export";get defaultConfig(){return{fileName:"export",includeHeaders:!0,onlyVisible:!0,onlySelected:!1}}isExportingFlag=!1;lastExportInfo=null;performExport(e,t){const s=this.config,l={format:e,fileName:t?.fileName??s.fileName??"export",includeHeaders:t?.includeHeaders??s.includeHeaders,processCell:t?.processCell,processHeader:t?.processHeader,columns:t?.columns,rowIndices:t?.rowIndices,excelStyles:t?.excelStyles,fileExtension:t?.fileExtension},i=function(e,t,n=!0){let o=e;if(n&&(o=o.filter(e=>!e.hidden&&!e.field.startsWith("__")&&!0!==e.meta?.utility)),t?.length){const e=new Set(t);o=o.filter(t=>e.has(t.field))}return o}(this.columns,t?.columns,s.onlyVisible);let c;if(t?.rowIndices)c=n(this.rows,t.rowIndices);else if(s.onlySelected){const e=this.getSelectionState();c=e?.selected?.size?n(this.rows,[...e.selected]):[...this.rows]}else c=[...this.rows];this.isExportingFlag=!0;let f=l.fileName;try{switch(e){case"csv":{const e=function(e,t,n,r={}){const s=r.delimiter??",",l=r.newline??"\n",i=[],c=r.bom?"\ufeff":"";if(!1!==n.includeHeaders){const e=t.map(e=>{const t=e.header||e.field;return o(n.processHeader?n.processHeader(t,e.field):t)});i.push(e.join(s))}for(const f of e){const e=t.map(e=>{let t=f[e.field];return n.processCell&&(t=n.processCell(t,e.field,f)),o(t)});i.push(e.join(s))}return c+i.join(l)}(c,i,l,{bom:!0});f=f.endsWith(".csv")?f:`${f}.csv`,function(e,t){r(new Blob([e],{type:"text/csv;charset=utf-8;"}),t)}(e,f);break}case"excel":{const e=p(c,i,l),t=l.fileExtension??".xls",n=t.startsWith(".")?t:`.${t}`;f=f.endsWith(n)?f:`${f}${n}`,function(e,t){r(new Blob([e],{type:"application/vnd.ms-excel;charset=utf-8;"}),t)}(e,f);break}case"json":{const e=c.map(e=>{const t={};for(const n of i){let o=e[n.field];l.processCell&&(o=l.processCell(o,n.field,e)),t[n.field]=o}return t}),t=JSON.stringify(e,null,2);f=f.endsWith(".json")?f:`${f}.json`;r(new Blob([t],{type:"application/json"}),f);break}}this.lastExportInfo={format:e,timestamp:new Date},this.emit("export-complete",{format:e,fileName:f,rowCount:c.length,columnCount:i.length})}finally{this.isExportingFlag=!1}}getSelectionState(){try{return this.grid?.getPluginState?.("selection")??null}catch{return null}}exportCsv(e){this.performExport("csv",e)}exportExcel(e){this.performExport("excel",e)}exportJson(e){this.performExport("json",e)}isExporting(){return this.isExportingFlag}getLastExport(){return this.lastExportInfo}}e.ExportPlugin=m,Object.defineProperty(e,Symbol.toStringTag,{value:"Module"})});
|
|
2
2
|
//# sourceMappingURL=export.umd.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"export.umd.js","sources":["../../../../../libs/grid/src/lib/plugins/shared/data-collection.ts","../../../../../libs/grid/src/lib/plugins/export/csv.ts","../../../../../libs/grid/src/lib/plugins/export/excel.ts","../../../../../libs/grid/src/lib/plugins/export/ExportPlugin.ts"],"sourcesContent":["/**\n * Shared Data Collection Utilities\n *\n * Pure functions for resolving columns and formatting values, shared between\n * the Clipboard and Export plugins. Each plugin bundles its own copy of this\n * module (no chunk splitting) since plugin builds inline sibling imports.\n *\n * @internal\n */\n\nimport type { ColumnConfig } from '../../core/types';\n\n/**\n * Resolve which columns to include in a data export or copy operation.\n *\n * Filters out hidden columns, utility columns (`meta.utility`), and\n * internal columns (`__`-prefixed fields). Optionally restricts to an\n * explicit set of field names.\n *\n * @param columns - All column configurations\n * @param fields - If provided, only include columns whose field is in this list\n * @param onlyVisible - When `true` (default), exclude hidden and internal columns\n * @returns Filtered column array preserving original order\n */\nexport function resolveColumns(\n columns: readonly ColumnConfig[],\n fields?: string[],\n onlyVisible = true,\n): ColumnConfig[] {\n let result = columns as ColumnConfig[];\n\n if (onlyVisible) {\n result = result.filter((c) => !c.hidden && !c.field.startsWith('__') && c.meta?.utility !== true);\n }\n\n if (fields?.length) {\n const fieldSet = new Set(fields);\n result = result.filter((c) => fieldSet.has(c.field));\n }\n\n return result;\n}\n\n/**\n * Resolve which rows to include, optionally filtered to specific indices.\n *\n * @param rows - All row data\n * @param indices - If provided, only include rows at these indices (sorted ascending)\n * @returns Filtered row array\n */\nexport function resolveRows<T>(rows: readonly T[], indices?: number[]): T[] {\n if (!indices?.length) return rows as T[];\n\n return [...indices]\n .sort((a, b) => a - b)\n .map((i) => rows[i])\n .filter((r): r is T => r != null);\n}\n\n/**\n * Format a raw cell value as a text string.\n *\n * Provides the common null / Date / object → string conversion shared by\n * both clipboard and export output builders.\n *\n * @param value - The cell value to format\n * @returns A plain-text representation of the value\n */\nexport function formatValueAsText(value: unknown): string {\n if (value == null) return '';\n if (value instanceof Date) return value.toISOString();\n if (typeof value === 'object') return JSON.stringify(value);\n return String(value);\n}\n","/**\n * CSV Export Utilities\n *\n * Functions for building and downloading CSV content.\n */\n\nimport type { ColumnConfig } from '../../core/types';\nimport type { ExportParams } from './types';\n\n/** CSV export options */\nexport interface CsvOptions {\n /** Field delimiter (default: ',') */\n delimiter?: string;\n /** Line separator (default: '\\n') */\n newline?: string;\n /** Whether to quote strings containing special characters (default: true) */\n quoteStrings?: boolean;\n /** Add UTF-8 BOM for Excel compatibility (default: false) */\n bom?: boolean;\n}\n\n/**\n * Format a value for CSV output.\n * Handles null, Date, objects, and strings with special characters.\n */\nexport function formatCsvValue(value: any, quote = true): string {\n if (value == null) return '';\n if (value instanceof Date) return value.toISOString();\n if (typeof value === 'object') return JSON.stringify(value);\n\n const str = String(value);\n\n // Quote if contains special characters (comma, quote, newline)\n if (quote && (str.includes(',') || str.includes('\"') || str.includes('\\n') || str.includes('\\r'))) {\n return `\"${str.replace(/\"/g, '\"\"')}\"`;\n }\n\n return str;\n}\n\n/**\n * Build CSV content from rows and columns.\n */\nexport function buildCsv(rows: any[], columns: ColumnConfig[], params: ExportParams, options: CsvOptions = {}): string {\n const delimiter = options.delimiter ?? ',';\n const newline = options.newline ?? '\\n';\n const lines: string[] = [];\n\n // UTF-8 BOM for Excel compatibility\n const bom = options.bom ? '\\uFEFF' : '';\n\n // Build header row\n if (params.includeHeaders !== false) {\n const headerRow = columns.map((col) => {\n const header = col.header || col.field;\n const processed = params.processHeader ? params.processHeader(header, col.field) : header;\n return formatCsvValue(processed);\n });\n lines.push(headerRow.join(delimiter));\n }\n\n // Build data rows\n for (const row of rows) {\n const cells = columns.map((col) => {\n let value = row[col.field];\n if (params.processCell) {\n value = params.processCell(value, col.field, row);\n }\n return formatCsvValue(value);\n });\n lines.push(cells.join(delimiter));\n }\n\n return bom + lines.join(newline);\n}\n\n/**\n * Download a Blob as a file.\n */\nexport function downloadBlob(blob: Blob, fileName: string): void {\n const url = URL.createObjectURL(blob);\n const link = document.createElement('a');\n link.href = url;\n link.download = fileName;\n link.style.display = 'none';\n document.body.appendChild(link);\n link.click();\n document.body.removeChild(link);\n URL.revokeObjectURL(url);\n}\n\n/**\n * Download CSV content as a file.\n */\nexport function downloadCsv(content: string, fileName: string): void {\n const blob = new Blob([content], { type: 'text/csv;charset=utf-8;' });\n downloadBlob(blob, fileName);\n}\n","/**\n * Excel Export Utilities\n *\n * Simple Excel XML format export (no external dependencies).\n * Produces XML Spreadsheet 2003 format which opens in Excel.\n */\n\nimport type { ColumnConfig } from '../../core/types';\nimport type { ExportParams } from './types';\nimport { downloadBlob } from './csv';\n\n/**\n * Escape XML special characters.\n */\nfunction escapeXml(str: string): string {\n return str\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n}\n\n/**\n * Build Excel XML content from rows and columns.\n * Uses XML Spreadsheet 2003 format for broad compatibility.\n */\nexport function buildExcelXml(rows: any[], columns: ColumnConfig[], params: ExportParams): string {\n let xml = `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<?mso-application progid=\"Excel.Sheet\"?>\n<Workbook xmlns=\"urn:schemas-microsoft-com:office:spreadsheet\"\n xmlns:ss=\"urn:schemas-microsoft-com:office:spreadsheet\">\n<Worksheet ss:Name=\"Sheet1\">\n<Table>`;\n\n // Build header row\n if (params.includeHeaders !== false) {\n xml += '\\n<Row>';\n for (const col of columns) {\n const header = col.header || col.field;\n const processed = params.processHeader ? params.processHeader(header, col.field) : header;\n xml += `<Cell><Data ss:Type=\"String\">${escapeXml(processed)}</Data></Cell>`;\n }\n xml += '</Row>';\n }\n\n // Build data rows\n for (const row of rows) {\n xml += '\\n<Row>';\n for (const col of columns) {\n let value = row[col.field];\n if (params.processCell) {\n value = params.processCell(value, col.field, row);\n }\n\n // Determine cell type based on value\n let type: 'Number' | 'String' | 'DateTime' = 'String';\n let displayValue = '';\n\n if (value == null) {\n displayValue = '';\n } else if (typeof value === 'number' && !isNaN(value)) {\n type = 'Number';\n displayValue = String(value);\n } else if (value instanceof Date) {\n type = 'DateTime';\n displayValue = value.toISOString();\n } else {\n displayValue = escapeXml(String(value));\n }\n\n xml += `<Cell><Data ss:Type=\"${type}\">${displayValue}</Data></Cell>`;\n }\n xml += '</Row>';\n }\n\n xml += '\\n</Table>\\n</Worksheet>\\n</Workbook>';\n return xml;\n}\n\n/**\n * Download Excel XML content as a file.\n */\nexport function downloadExcel(content: string, fileName: string): void {\n const finalName = fileName.endsWith('.xls') ? fileName : `${fileName}.xls`;\n const blob = new Blob([content], {\n type: 'application/vnd.ms-excel;charset=utf-8;',\n });\n downloadBlob(blob, finalName);\n}\n","/**\n * Export Plugin (Class-based)\n *\n * Provides data export functionality for tbw-grid.\n * Supports CSV, Excel (XML), and JSON formats.\n */\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n\nimport { BaseGridPlugin } from '../../core/plugin/base-plugin';\nimport type { ColumnConfig } from '../../core/types';\nimport { resolveColumns, resolveRows } from '../shared/data-collection';\nimport { buildCsv, downloadBlob, downloadCsv } from './csv';\nimport { buildExcelXml, downloadExcel } from './excel';\nimport type { ExportCompleteDetail, ExportConfig, ExportFormat, ExportParams } from './types';\n\n/** Selection plugin state interface for type safety */\ninterface SelectionPluginState {\n selected: Set<number>;\n}\n\n/**\n * Export Plugin for tbw-grid\n *\n * Lets users download grid data as CSV, Excel (XML), or JSON with a single click\n * or API call. Great for reporting, data backup, or letting users work with data\n * in Excel. Integrates with SelectionPlugin to export only selected rows.\n *\n * ## Installation\n *\n * ```ts\n * import { ExportPlugin } from '@toolbox-web/grid/plugins/export';\n * ```\n *\n * ## Supported Formats\n *\n * | Format | Method | Description |\n * |--------|--------|-------------|\n * | CSV | `exportToCSV()` | Comma-separated values |\n * | Excel | `exportToExcel()` | Excel XML format (.xlsx) |\n * | JSON | `exportToJSON()` | JSON array of objects |\n *\n * @example Basic Export with Button\n * ```ts\n * import { queryGrid } from '@toolbox-web/grid';\n * import { ExportPlugin } from '@toolbox-web/grid/plugins/export';\n *\n * const grid = queryGrid('tbw-grid');\n * grid.gridConfig = {\n * columns: [\n * { field: 'name', header: 'Name' },\n * { field: 'email', header: 'Email' },\n * ],\n * plugins: [new ExportPlugin({ fileName: 'employees', includeHeaders: true })],\n * };\n *\n * // Trigger export via button\n * document.getElementById('export-btn').addEventListener('click', () => {\n * grid.getPluginByName('export').exportToCSV();\n * });\n * ```\n *\n * @example Export Selected Rows Only\n * ```ts\n * import { SelectionPlugin } from '@toolbox-web/grid/plugins/selection';\n *\n * grid.gridConfig = {\n * plugins: [\n * new SelectionPlugin({ mode: 'row' }),\n * new ExportPlugin({ onlySelected: true }),\n * ],\n * };\n * ```\n *\n * @see {@link ExportConfig} for all configuration options\n * @see {@link ExportParams} for method parameters\n * @see SelectionPlugin for exporting selected rows\n *\n * @internal Extends BaseGridPlugin\n */\nexport class ExportPlugin extends BaseGridPlugin<ExportConfig> {\n /** @internal */\n readonly name = 'export';\n\n /** @internal */\n protected override get defaultConfig(): Partial<ExportConfig> {\n return {\n fileName: 'export',\n includeHeaders: true,\n onlyVisible: true,\n onlySelected: false,\n };\n }\n\n // #region Internal State\n private isExportingFlag = false;\n private lastExportInfo: { format: ExportFormat; timestamp: Date } | null = null;\n // #endregion\n\n // #region Private Methods\n\n private performExport(format: ExportFormat, params?: Partial<ExportParams>): void {\n const config = this.config;\n\n // Build full params with defaults\n const fullParams: ExportParams = {\n format,\n fileName: params?.fileName ?? config.fileName ?? 'export',\n includeHeaders: params?.includeHeaders ?? config.includeHeaders,\n processCell: params?.processCell,\n processHeader: params?.processHeader,\n columns: params?.columns,\n rowIndices: params?.rowIndices,\n };\n\n // Get columns to export (shared utility handles hidden/utility filtering)\n const columns = resolveColumns(this.columns, params?.columns, config.onlyVisible) as ColumnConfig[];\n\n // Get rows to export\n let rows: Record<string, unknown>[];\n if (params?.rowIndices) {\n rows = resolveRows(this.rows as Record<string, unknown>[], params.rowIndices);\n } else if (config.onlySelected) {\n const selectionState = this.getSelectionState();\n if (selectionState?.selected?.size) {\n rows = resolveRows(this.rows as Record<string, unknown>[], [...selectionState.selected]);\n } else {\n rows = [...this.rows] as Record<string, unknown>[];\n }\n } else {\n rows = [...this.rows] as Record<string, unknown>[];\n }\n\n this.isExportingFlag = true;\n let fileName = fullParams.fileName!;\n\n try {\n switch (format) {\n case 'csv': {\n const content = buildCsv(rows, columns, fullParams, { bom: true });\n fileName = fileName.endsWith('.csv') ? fileName : `${fileName}.csv`;\n downloadCsv(content, fileName);\n break;\n }\n\n case 'excel': {\n const content = buildExcelXml(rows, columns, fullParams);\n fileName = fileName.endsWith('.xls') ? fileName : `${fileName}.xls`;\n downloadExcel(content, fileName);\n break;\n }\n\n case 'json': {\n const jsonData = rows.map((row) => {\n const obj: Record<string, any> = {};\n for (const col of columns) {\n let value = row[col.field];\n if (fullParams.processCell) {\n value = fullParams.processCell(value, col.field, row);\n }\n obj[col.field] = value;\n }\n return obj;\n });\n const content = JSON.stringify(jsonData, null, 2);\n fileName = fileName.endsWith('.json') ? fileName : `${fileName}.json`;\n const blob = new Blob([content], { type: 'application/json' });\n downloadBlob(blob, fileName);\n break;\n }\n }\n\n this.lastExportInfo = { format, timestamp: new Date() };\n\n this.emit<ExportCompleteDetail>('export-complete', {\n format,\n fileName,\n rowCount: rows.length,\n columnCount: columns.length,\n });\n } finally {\n this.isExportingFlag = false;\n }\n }\n\n private getSelectionState(): SelectionPluginState | null {\n try {\n return (this.grid?.getPluginState?.('selection') as SelectionPluginState | null) ?? null;\n } catch {\n return null;\n }\n }\n // #endregion\n\n // #region Public API\n\n /**\n * Export data to CSV format.\n * @param params - Optional export parameters\n */\n exportCsv(params?: Partial<ExportParams>): void {\n this.performExport('csv', params);\n }\n\n /**\n * Export data to Excel format (XML Spreadsheet).\n * @param params - Optional export parameters\n */\n exportExcel(params?: Partial<ExportParams>): void {\n this.performExport('excel', params);\n }\n\n /**\n * Export data to JSON format.\n * @param params - Optional export parameters\n */\n exportJson(params?: Partial<ExportParams>): void {\n this.performExport('json', params);\n }\n\n /**\n * Check if an export is currently in progress.\n * @returns Whether export is in progress\n */\n isExporting(): boolean {\n return this.isExportingFlag;\n }\n\n /**\n * Get information about the last export.\n * @returns Export info or null if no export has occurred\n */\n getLastExport(): { format: ExportFormat; timestamp: Date } | null {\n return this.lastExportInfo;\n }\n // #endregion\n}\n"],"names":["resolveRows","rows","indices","length","sort","a","b","map","i","filter","r","formatCsvValue","value","quote","Date","toISOString","JSON","stringify","str","String","includes","replace","downloadBlob","blob","fileName","url","URL","createObjectURL","link","document","createElement","href","download","style","display","body","appendChild","click","removeChild","revokeObjectURL","escapeXml","ExportPlugin","BaseGridPlugin","name","defaultConfig","includeHeaders","onlyVisible","onlySelected","isExportingFlag","lastExportInfo","performExport","format","params","config","this","fullParams","processCell","processHeader","columns","rowIndices","fields","result","c","hidden","field","startsWith","meta","utility","fieldSet","Set","has","resolveColumns","selectionState","getSelectionState","selected","size","content","options","delimiter","newline","lines","bom","headerRow","col","header","push","join","row","cells","buildCsv","endsWith","Blob","type","downloadCsv","xml","displayValue","isNaN","buildExcelXml","finalName","downloadExcel","jsonData","obj","timestamp","emit","rowCount","columnCount","grid","getPluginState","exportCsv","exportExcel","exportJson","isExporting","getLastExport"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"export.umd.js","sources":["../../../../../libs/grid/src/lib/plugins/shared/data-collection.ts","../../../../../libs/grid/src/lib/plugins/export/csv.ts","../../../../../libs/grid/src/lib/plugins/export/excel-styles.ts","../../../../../libs/grid/src/lib/plugins/export/excel.ts","../../../../../libs/grid/src/lib/plugins/export/ExportPlugin.ts"],"sourcesContent":["/**\n * Shared Data Collection Utilities\n *\n * Pure functions for resolving columns and formatting values, shared between\n * the Clipboard and Export plugins. Each plugin bundles its own copy of this\n * module (no chunk splitting) since plugin builds inline sibling imports.\n *\n * @internal\n */\n\nimport type { ColumnConfig } from '../../core/types';\n\n/**\n * Resolve which columns to include in a data export or copy operation.\n *\n * Filters out hidden columns, utility columns (`meta.utility`), and\n * internal columns (`__`-prefixed fields). Optionally restricts to an\n * explicit set of field names.\n *\n * @param columns - All column configurations\n * @param fields - If provided, only include columns whose field is in this list\n * @param onlyVisible - When `true` (default), exclude hidden and internal columns\n * @returns Filtered column array preserving original order\n */\nexport function resolveColumns(\n columns: readonly ColumnConfig[],\n fields?: string[],\n onlyVisible = true,\n): ColumnConfig[] {\n let result = columns as ColumnConfig[];\n\n if (onlyVisible) {\n result = result.filter((c) => !c.hidden && !c.field.startsWith('__') && c.meta?.utility !== true);\n }\n\n if (fields?.length) {\n const fieldSet = new Set(fields);\n result = result.filter((c) => fieldSet.has(c.field));\n }\n\n return result;\n}\n\n/**\n * Resolve which rows to include, optionally filtered to specific indices.\n *\n * @param rows - All row data\n * @param indices - If provided, only include rows at these indices (sorted ascending)\n * @returns Filtered row array\n */\nexport function resolveRows<T>(rows: readonly T[], indices?: number[]): T[] {\n if (!indices?.length) return rows as T[];\n\n return [...indices]\n .sort((a, b) => a - b)\n .map((i) => rows[i])\n .filter((r): r is T => r != null);\n}\n\n/**\n * Format a raw cell value as a text string.\n *\n * Provides the common null / Date / object → string conversion shared by\n * both clipboard and export output builders.\n *\n * @param value - The cell value to format\n * @returns A plain-text representation of the value\n */\nexport function formatValueAsText(value: unknown): string {\n if (value == null) return '';\n if (value instanceof Date) return value.toISOString();\n if (typeof value === 'object') return JSON.stringify(value);\n return String(value);\n}\n","/**\n * CSV Export Utilities\n *\n * Functions for building and downloading CSV content.\n */\n\nimport type { ColumnConfig } from '../../core/types';\nimport type { ExportParams } from './types';\n\n/** CSV export options */\nexport interface CsvOptions {\n /** Field delimiter (default: ',') */\n delimiter?: string;\n /** Line separator (default: '\\n') */\n newline?: string;\n /** Whether to quote strings containing special characters (default: true) */\n quoteStrings?: boolean;\n /** Add UTF-8 BOM for Excel compatibility (default: false) */\n bom?: boolean;\n}\n\n/**\n * Format a value for CSV output.\n * Handles null, Date, objects, and strings with special characters.\n */\nexport function formatCsvValue(value: any, quote = true): string {\n if (value == null) return '';\n if (value instanceof Date) return value.toISOString();\n if (typeof value === 'object') return JSON.stringify(value);\n\n const str = String(value);\n\n // Quote if contains special characters (comma, quote, newline)\n if (quote && (str.includes(',') || str.includes('\"') || str.includes('\\n') || str.includes('\\r'))) {\n return `\"${str.replace(/\"/g, '\"\"')}\"`;\n }\n\n return str;\n}\n\n/**\n * Build CSV content from rows and columns.\n */\nexport function buildCsv(rows: any[], columns: ColumnConfig[], params: ExportParams, options: CsvOptions = {}): string {\n const delimiter = options.delimiter ?? ',';\n const newline = options.newline ?? '\\n';\n const lines: string[] = [];\n\n // UTF-8 BOM for Excel compatibility\n const bom = options.bom ? '\\uFEFF' : '';\n\n // Build header row\n if (params.includeHeaders !== false) {\n const headerRow = columns.map((col) => {\n const header = col.header || col.field;\n const processed = params.processHeader ? params.processHeader(header, col.field) : header;\n return formatCsvValue(processed);\n });\n lines.push(headerRow.join(delimiter));\n }\n\n // Build data rows\n for (const row of rows) {\n const cells = columns.map((col) => {\n let value = row[col.field];\n if (params.processCell) {\n value = params.processCell(value, col.field, row);\n }\n return formatCsvValue(value);\n });\n lines.push(cells.join(delimiter));\n }\n\n return bom + lines.join(newline);\n}\n\n/**\n * Download a Blob as a file.\n */\nexport function downloadBlob(blob: Blob, fileName: string): void {\n const url = URL.createObjectURL(blob);\n const link = document.createElement('a');\n link.href = url;\n link.download = fileName;\n link.style.display = 'none';\n document.body.appendChild(link);\n link.click();\n document.body.removeChild(link);\n URL.revokeObjectURL(url);\n}\n\n/**\n * Download CSV content as a file.\n */\nexport function downloadCsv(content: string, fileName: string): void {\n const blob = new Blob([content], { type: 'text/csv;charset=utf-8;' });\n downloadBlob(blob, fileName);\n}\n","/**\n * Excel Style Engine\n *\n * Builds a deduplicated `<Styles>` block for XML Spreadsheet 2003.\n * Collects unique ExcelCellStyle objects, assigns ss:StyleID values,\n * and generates the corresponding XML.\n */\n\nimport type { ExcelBorder, ExcelCellStyle, ExcelStyleConfig } from './types';\n\n// #region Style Hashing\n\n/**\n * Produce a deterministic string key for a style object.\n * Two structurally identical styles produce the same hash.\n * Recursively sorts object keys for order-independent comparison.\n */\nfunction hashStyle(style: ExcelCellStyle): string {\n return JSON.stringify(sortKeys(style));\n}\n\n/** Recursively sort object keys for deterministic serialization. */\nfunction sortKeys(obj: unknown): unknown {\n if (obj == null || typeof obj !== 'object') return obj;\n if (Array.isArray(obj)) return obj.map(sortKeys);\n const sorted: Record<string, unknown> = {};\n for (const key of Object.keys(obj).sort()) {\n sorted[key] = sortKeys((obj as Record<string, unknown>)[key]);\n }\n return sorted;\n}\n\n// #endregion\n\n// #region Style Registry\n\n/** Maps a hash → style ID and the original style. */\nexport interface StyleEntry {\n id: string;\n style: ExcelCellStyle;\n}\n\n/**\n * Collects unique styles and assigns each an `ss:StyleID`.\n * Register styles via `register()`, then call `getStyleId()` to look up\n * the ID for a given style, and `toXml()` to emit the `<Styles>` block.\n */\nexport class StyleRegistry {\n /** hash → StyleEntry */\n #entries = new Map<string, StyleEntry>();\n #counter = 0;\n\n /**\n * Register a style and return its assigned ID.\n * If an identical style was already registered, returns the existing ID.\n */\n register(style: ExcelCellStyle): string {\n const hash = hashStyle(style);\n const existing = this.#entries.get(hash);\n if (existing) return existing.id;\n\n this.#counter++;\n const id = `s${this.#counter}`;\n this.#entries.set(hash, { id, style });\n return id;\n }\n\n /** Look up the ID previously assigned to a style (or undefined). */\n getStyleId(style: ExcelCellStyle): string | undefined {\n return this.#entries.get(hashStyle(style))?.id;\n }\n\n /** Number of unique styles registered so far. */\n get size(): number {\n return this.#entries.size;\n }\n\n /** Emit the full `<Styles>…</Styles>` XML fragment, or empty string if no styles. */\n toXml(): string {\n if (this.#entries.size === 0) return '';\n\n let xml = '\\n<Styles>';\n for (const { id, style } of this.#entries.values()) {\n xml += buildStyleElement(id, style);\n }\n xml += '\\n</Styles>';\n return xml;\n }\n}\n\n// #endregion\n\n// #region XML Builders\n\nfunction buildStyleElement(id: string, style: ExcelCellStyle): string {\n let xml = `\\n<Style ss:ID=\"${id}\">`;\n\n if (style.font) {\n xml += '<Font';\n if (style.font.name) xml += ` ss:FontName=\"${style.font.name}\"`;\n if (style.font.size) xml += ` ss:Size=\"${style.font.size}\"`;\n if (style.font.bold) xml += ' ss:Bold=\"1\"';\n if (style.font.italic) xml += ' ss:Italic=\"1\"';\n if (style.font.color) xml += ` ss:Color=\"${style.font.color}\"`;\n xml += '/>';\n }\n\n if (style.fill) {\n const pattern = style.fill.pattern ?? 'Solid';\n xml += `<Interior ss:Color=\"${style.fill.color}\" ss:Pattern=\"${pattern}\"/>`;\n }\n\n if (style.numberFormat) {\n xml += `<NumberFormat ss:Format=\"${style.numberFormat}\"/>`;\n }\n\n if (style.alignment) {\n xml += '<Alignment';\n if (style.alignment.horizontal) xml += ` ss:Horizontal=\"${style.alignment.horizontal}\"`;\n if (style.alignment.vertical) xml += ` ss:Vertical=\"${style.alignment.vertical}\"`;\n if (style.alignment.wrapText) xml += ' ss:WrapText=\"1\"';\n xml += '/>';\n }\n\n if (style.borders) {\n xml += '<Borders>';\n if (style.borders.top) xml += buildBorderElement('Top', style.borders.top);\n if (style.borders.bottom) xml += buildBorderElement('Bottom', style.borders.bottom);\n if (style.borders.left) xml += buildBorderElement('Left', style.borders.left);\n if (style.borders.right) xml += buildBorderElement('Right', style.borders.right);\n xml += '</Borders>';\n }\n\n xml += '</Style>';\n return xml;\n}\n\nfunction buildBorderElement(position: string, border: ExcelBorder): string {\n let xml = `<Border ss:Position=\"${position}\" ss:LineStyle=\"Continuous\" ss:Weight=\"${borderWeight(border.style)}\"`;\n if (border.color) xml += ` ss:Color=\"${border.color}\"`;\n xml += '/>';\n return xml;\n}\n\nfunction borderWeight(style: ExcelBorder['style']): number {\n switch (style) {\n case 'Thin':\n return 1;\n case 'Medium':\n return 2;\n case 'Thick':\n return 3;\n }\n}\n\n// #endregion\n\n// #region Style Resolution\n\n/**\n * Build a StyleRegistry pre-populated with all styles declared in the config.\n * This covers headerStyle, defaultStyle, and columnStyles.\n * (cellStyle callbacks are resolved per-cell at render time.)\n */\nexport function buildStyleRegistry(config: ExcelStyleConfig): StyleRegistry {\n const registry = new StyleRegistry();\n\n if (config.headerStyle) registry.register(config.headerStyle);\n\n if (config.defaultStyle) registry.register(config.defaultStyle);\n\n if (config.columnStyles) {\n for (const style of Object.values(config.columnStyles)) {\n registry.register(style);\n }\n }\n\n return registry;\n}\n\n/**\n * Resolve the style ID for a data cell. Precedence (highest → lowest):\n * 1. cellStyle callback return value\n * 2. columnStyles[field]\n * 3. defaultStyle\n */\nexport function resolveDataStyleId(\n registry: StyleRegistry,\n config: ExcelStyleConfig,\n value: unknown,\n field: string,\n row: unknown,\n): string | undefined {\n // 1. Dynamic cell callback\n if (config.cellStyle) {\n const dynamic = config.cellStyle(value, field, row);\n if (dynamic) {\n // Register on-the-fly (dedup handles repeats)\n return registry.register(dynamic);\n }\n }\n\n // 2. Per-column override\n const colStyle = config.columnStyles?.[field];\n if (colStyle) return registry.getStyleId(colStyle);\n\n // 3. Default\n if (config.defaultStyle) return registry.getStyleId(config.defaultStyle);\n\n return undefined;\n}\n\n// #endregion\n\n// #region Column Widths\n\n/** Character width → Excel column width approximation (px ≈ chars × 7) */\nconst CHAR_WIDTH = 7;\n\n/**\n * Build `<Column>` elements for explicit or auto-fit widths.\n */\nexport function buildColumnWidthsXml(\n columns: { field: string; header?: string }[],\n rows: Record<string, unknown>[],\n config: ExcelStyleConfig,\n): string {\n const widths = config.columnWidths;\n const autoFit = config.autoFitColumns;\n\n if (!widths && !autoFit) return '';\n\n let xml = '';\n for (const col of columns) {\n let width: number | undefined = widths?.[col.field];\n\n if (width == null && autoFit) {\n width = autoFitWidth(col, rows);\n }\n\n if (width != null) {\n xml += `\\n<Column ss:Width=\"${width * CHAR_WIDTH}\"/>`;\n } else {\n // Emit an empty column entry to keep ordinal alignment\n xml += '\\n<Column/>';\n }\n }\n\n return xml;\n}\n\n/** Estimate width from header + first N data rows (capped at 50). */\nfunction autoFitWidth(col: { field: string; header?: string }, rows: Record<string, unknown>[]): number {\n const sampleSize = Math.min(rows.length, 50);\n let maxLen = (col.header ?? col.field).length;\n\n for (let i = 0; i < sampleSize; i++) {\n const val = rows[i][col.field];\n const len = val == null ? 0 : String(val).length;\n if (len > maxLen) maxLen = len;\n }\n\n // Add a small padding\n return maxLen + 2;\n}\n\n// #endregion\n","/**\n * Excel Export Utilities\n *\n * Simple Excel XML format export (no external dependencies).\n * Produces XML Spreadsheet 2003 format which opens in Excel.\n */\n\nimport type { ColumnConfig } from '../../core/types';\nimport { downloadBlob } from './csv';\nimport { buildColumnWidthsXml, buildStyleRegistry, resolveDataStyleId } from './excel-styles';\nimport type { ExportParams } from './types';\n\n/**\n * Escape XML special characters.\n */\nfunction escapeXml(str: string): string {\n return str\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n}\n\n/**\n * Build Excel XML content from rows and columns.\n * Uses XML Spreadsheet 2003 format for broad compatibility.\n */\nexport function buildExcelXml(rows: any[], columns: ColumnConfig[], params: ExportParams): string {\n const styles = params.excelStyles;\n const registry = styles ? buildStyleRegistry(styles) : undefined;\n\n let xml = `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<?mso-application progid=\"Excel.Sheet\"?>\n<Workbook xmlns=\"urn:schemas-microsoft-com:office:spreadsheet\"\n xmlns:ss=\"urn:schemas-microsoft-com:office:spreadsheet\">`;\n\n // Emit <Styles> block (only when styles are configured)\n if (registry) {\n // Pre-register dynamic cellStyle entries so they appear in <Styles>\n if (styles!.cellStyle) {\n for (const row of rows) {\n for (const col of columns) {\n const value = row[col.field];\n const dynamic = styles!.cellStyle(value, col.field, row);\n if (dynamic) registry.register(dynamic);\n }\n }\n }\n xml += registry.toXml();\n }\n\n xml += '\\n<Worksheet ss:Name=\"Sheet1\">\\n<Table>';\n\n // Column widths\n if (styles) {\n xml += buildColumnWidthsXml(columns, rows as Record<string, unknown>[], styles);\n }\n\n // Header style ID\n const headerStyleId = styles?.headerStyle && registry ? registry.getStyleId(styles.headerStyle) : undefined;\n\n // Build header row\n if (params.includeHeaders !== false) {\n xml += '\\n<Row>';\n for (const col of columns) {\n const header = col.header || col.field;\n const processed = params.processHeader ? params.processHeader(header, col.field) : header;\n const styleAttr = headerStyleId ? ` ss:StyleID=\"${headerStyleId}\"` : '';\n xml += `<Cell${styleAttr}><Data ss:Type=\"String\">${escapeXml(processed)}</Data></Cell>`;\n }\n xml += '</Row>';\n }\n\n // Build data rows\n for (const row of rows) {\n xml += '\\n<Row>';\n for (const col of columns) {\n let value = row[col.field];\n if (params.processCell) {\n value = params.processCell(value, col.field, row);\n }\n\n // Determine cell type based on value\n let type: 'Number' | 'String' | 'DateTime' = 'String';\n let displayValue = '';\n\n if (value == null) {\n displayValue = '';\n } else if (typeof value === 'number' && !isNaN(value)) {\n type = 'Number';\n displayValue = String(value);\n } else if (value instanceof Date) {\n type = 'DateTime';\n displayValue = value.toISOString();\n } else {\n displayValue = escapeXml(String(value));\n }\n\n // Resolve data cell style\n const dataStyleId = registry && styles ? resolveDataStyleId(registry, styles, value, col.field, row) : undefined;\n const styleAttr = dataStyleId ? ` ss:StyleID=\"${dataStyleId}\"` : '';\n\n xml += `<Cell${styleAttr}><Data ss:Type=\"${type}\">${displayValue}</Data></Cell>`;\n }\n xml += '</Row>';\n }\n\n xml += '\\n</Table>\\n</Worksheet>\\n</Workbook>';\n return xml;\n}\n\n/**\n * Download Excel XML content as a file.\n */\nexport function downloadExcel(content: string, fileName: string): void {\n const blob = new Blob([content], {\n type: 'application/vnd.ms-excel;charset=utf-8;',\n });\n downloadBlob(blob, fileName);\n}\n","/**\n * Export Plugin (Class-based)\n *\n * Provides data export functionality for tbw-grid.\n * Supports CSV, Excel (XML), and JSON formats.\n */\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n\nimport { BaseGridPlugin } from '../../core/plugin/base-plugin';\nimport type { ColumnConfig } from '../../core/types';\nimport { resolveColumns, resolveRows } from '../shared/data-collection';\nimport { buildCsv, downloadBlob, downloadCsv } from './csv';\nimport { buildExcelXml, downloadExcel } from './excel';\nimport type { ExportCompleteDetail, ExportConfig, ExportFormat, ExportParams } from './types';\n\n/** Selection plugin state interface for type safety */\ninterface SelectionPluginState {\n selected: Set<number>;\n}\n\n/**\n * Export Plugin for tbw-grid\n *\n * Lets users download grid data as CSV, Excel (XML), or JSON with a single click\n * or API call. Great for reporting, data backup, or letting users work with data\n * in Excel. Integrates with SelectionPlugin to export only selected rows.\n *\n * ## Installation\n *\n * ```ts\n * import { ExportPlugin } from '@toolbox-web/grid/plugins/export';\n * ```\n *\n * ## Supported Formats\n *\n * | Format | Method | Description |\n * |--------|--------|-------------|\n * | CSV | `exportToCSV()` | Comma-separated values |\n * | Excel | `exportToExcel()` | Excel XML format (.xlsx) |\n * | JSON | `exportToJSON()` | JSON array of objects |\n *\n * @example Basic Export with Button\n * ```ts\n * import { queryGrid } from '@toolbox-web/grid';\n * import { ExportPlugin } from '@toolbox-web/grid/plugins/export';\n *\n * const grid = queryGrid('tbw-grid');\n * grid.gridConfig = {\n * columns: [\n * { field: 'name', header: 'Name' },\n * { field: 'email', header: 'Email' },\n * ],\n * plugins: [new ExportPlugin({ fileName: 'employees', includeHeaders: true })],\n * };\n *\n * // Trigger export via button\n * document.getElementById('export-btn').addEventListener('click', () => {\n * grid.getPluginByName('export').exportToCSV();\n * });\n * ```\n *\n * @example Export Selected Rows Only\n * ```ts\n * import { SelectionPlugin } from '@toolbox-web/grid/plugins/selection';\n *\n * grid.gridConfig = {\n * plugins: [\n * new SelectionPlugin({ mode: 'row' }),\n * new ExportPlugin({ onlySelected: true }),\n * ],\n * };\n * ```\n *\n * @see {@link ExportConfig} for all configuration options\n * @see {@link ExportParams} for method parameters\n * @see SelectionPlugin for exporting selected rows\n *\n * @internal Extends BaseGridPlugin\n */\nexport class ExportPlugin extends BaseGridPlugin<ExportConfig> {\n /** @internal */\n readonly name = 'export';\n\n /** @internal */\n protected override get defaultConfig(): Partial<ExportConfig> {\n return {\n fileName: 'export',\n includeHeaders: true,\n onlyVisible: true,\n onlySelected: false,\n };\n }\n\n // #region Internal State\n private isExportingFlag = false;\n private lastExportInfo: { format: ExportFormat; timestamp: Date } | null = null;\n // #endregion\n\n // #region Private Methods\n\n private performExport(format: ExportFormat, params?: Partial<ExportParams>): void {\n const config = this.config;\n\n // Build full params with defaults\n const fullParams: ExportParams = {\n format,\n fileName: params?.fileName ?? config.fileName ?? 'export',\n includeHeaders: params?.includeHeaders ?? config.includeHeaders,\n processCell: params?.processCell,\n processHeader: params?.processHeader,\n columns: params?.columns,\n rowIndices: params?.rowIndices,\n excelStyles: params?.excelStyles,\n fileExtension: params?.fileExtension,\n };\n\n // Get columns to export (shared utility handles hidden/utility filtering)\n const columns = resolveColumns(this.columns, params?.columns, config.onlyVisible) as ColumnConfig[];\n\n // Get rows to export\n let rows: Record<string, unknown>[];\n if (params?.rowIndices) {\n rows = resolveRows(this.rows as Record<string, unknown>[], params.rowIndices);\n } else if (config.onlySelected) {\n const selectionState = this.getSelectionState();\n if (selectionState?.selected?.size) {\n rows = resolveRows(this.rows as Record<string, unknown>[], [...selectionState.selected]);\n } else {\n rows = [...this.rows] as Record<string, unknown>[];\n }\n } else {\n rows = [...this.rows] as Record<string, unknown>[];\n }\n\n this.isExportingFlag = true;\n let fileName = fullParams.fileName!;\n\n try {\n switch (format) {\n case 'csv': {\n const content = buildCsv(rows, columns, fullParams, { bom: true });\n fileName = fileName.endsWith('.csv') ? fileName : `${fileName}.csv`;\n downloadCsv(content, fileName);\n break;\n }\n\n case 'excel': {\n const content = buildExcelXml(rows, columns, fullParams);\n const ext = fullParams.fileExtension ?? '.xls';\n const normalizedExt = ext.startsWith('.') ? ext : `.${ext}`;\n fileName = fileName.endsWith(normalizedExt) ? fileName : `${fileName}${normalizedExt}`;\n downloadExcel(content, fileName);\n break;\n }\n\n case 'json': {\n const jsonData = rows.map((row) => {\n const obj: Record<string, any> = {};\n for (const col of columns) {\n let value = row[col.field];\n if (fullParams.processCell) {\n value = fullParams.processCell(value, col.field, row);\n }\n obj[col.field] = value;\n }\n return obj;\n });\n const content = JSON.stringify(jsonData, null, 2);\n fileName = fileName.endsWith('.json') ? fileName : `${fileName}.json`;\n const blob = new Blob([content], { type: 'application/json' });\n downloadBlob(blob, fileName);\n break;\n }\n }\n\n this.lastExportInfo = { format, timestamp: new Date() };\n\n this.emit<ExportCompleteDetail>('export-complete', {\n format,\n fileName,\n rowCount: rows.length,\n columnCount: columns.length,\n });\n } finally {\n this.isExportingFlag = false;\n }\n }\n\n private getSelectionState(): SelectionPluginState | null {\n try {\n return (this.grid?.getPluginState?.('selection') as SelectionPluginState | null) ?? null;\n } catch {\n return null;\n }\n }\n // #endregion\n\n // #region Public API\n\n /**\n * Export data to CSV format.\n * @param params - Optional export parameters\n */\n exportCsv(params?: Partial<ExportParams>): void {\n this.performExport('csv', params);\n }\n\n /**\n * Export data to Excel format (XML Spreadsheet).\n * @param params - Optional export parameters\n */\n exportExcel(params?: Partial<ExportParams>): void {\n this.performExport('excel', params);\n }\n\n /**\n * Export data to JSON format.\n * @param params - Optional export parameters\n */\n exportJson(params?: Partial<ExportParams>): void {\n this.performExport('json', params);\n }\n\n /**\n * Check if an export is currently in progress.\n * @returns Whether export is in progress\n */\n isExporting(): boolean {\n return this.isExportingFlag;\n }\n\n /**\n * Get information about the last export.\n * @returns Export info or null if no export has occurred\n */\n getLastExport(): { format: ExportFormat; timestamp: Date } | null {\n return this.lastExportInfo;\n }\n // #endregion\n}\n"],"names":["resolveRows","rows","indices","length","sort","a","b","map","i","filter","r","formatCsvValue","value","quote","Date","toISOString","JSON","stringify","str","String","includes","replace","downloadBlob","blob","fileName","url","URL","createObjectURL","link","document","createElement","href","download","style","display","body","appendChild","click","removeChild","revokeObjectURL","hashStyle","sortKeys","obj","Array","isArray","sorted","key","Object","keys","StyleRegistry","entries","Map","counter","register","hash","existing","this","get","id","set","getStyleId","size","toXml","xml","values","buildStyleElement","font","name","bold","italic","color","fill","pattern","numberFormat","alignment","horizontal","vertical","wrapText","borders","top","buildBorderElement","bottom","left","right","position","border","borderWeight","resolveDataStyleId","registry","config","field","row","cellStyle","dynamic","colStyle","columnStyles","defaultStyle","autoFitWidth","col","sampleSize","Math","min","maxLen","header","val","len","escapeXml","buildExcelXml","columns","params","styles","excelStyles","headerStyle","buildStyleRegistry","widths","columnWidths","autoFit","autoFitColumns","width","buildColumnWidthsXml","headerStyleId","includeHeaders","processHeader","processCell","type","displayValue","isNaN","dataStyleId","ExportPlugin","BaseGridPlugin","defaultConfig","onlyVisible","onlySelected","isExportingFlag","lastExportInfo","performExport","format","fullParams","rowIndices","fileExtension","fields","result","c","hidden","startsWith","meta","utility","fieldSet","Set","has","resolveColumns","selectionState","getSelectionState","selected","content","options","delimiter","newline","lines","bom","headerRow","push","join","cells","buildCsv","endsWith","Blob","downloadCsv","ext","normalizedExt","downloadExcel","jsonData","timestamp","emit","rowCount","columnCount","grid","getPluginState","exportCsv","exportExcel","exportJson","isExporting","getLastExport"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("../../core/constants"),require("../../core/internal/aggregators"),require("../../core/internal/aria"),require("../../core/plugin/base-plugin"),require("../../core/plugin/expander-column")):"function"==typeof define&&define.amd?define(["exports","../../core/constants","../../core/internal/aggregators","../../core/internal/aria","../../core/plugin/base-plugin","../../core/plugin/expander-column"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).TbwGridPlugin_groupingRows={},e.TbwGrid,e.TbwGrid,e.TbwGrid,e.TbwGrid,e.TbwGrid)}(this,function(e,t,n,r,o,i){"use strict";function s({rows:e,config:t,expanded:n,initialExpanded:r}){const o=t.groupOn;if("function"!=typeof o)return[];const i={key:"__root__",value:null,depth:-1,rows:[],children:new Map};if(e.forEach(e=>{let t=o(e);null==t||!1===t?t=["__ungrouped__"]:Array.isArray(t)||(t=[t]);let n=i;t.forEach((t,r)=>{const o=null==t?"∅":String(t),i="__root__"===n.key?o:n.key+"||"+o;let s=n.children.get(o);s||(s={key:i,value:t,depth:r,rows:[],children:new Map,parent:n},n.children.set(o,s)),s.rows.push(e),n=s})}),1===i.children.size&&i.children.has("__ungrouped__")){if(i.children.get("__ungrouped__").rows.length===e.length)return[]}const s=new Map;for(let u=0;u<e.length;u++)s.set(e[u],u);const a=new Set([...n,...r??[]]),d=[],l=e=>{if(e===i)return void e.children.forEach(e=>l(e));const t=a.has(e.key);d.push({kind:"group",key:e.key,value:e.value,depth:e.depth,rows:e.rows,expanded:t}),t&&(e.children.size?e.children.forEach(e=>l(e)):e.rows.forEach(e=>d.push({kind:"data",row:e,rowIndex:s.get(e)??-1})))};return l(i),d}class a extends o.BaseGridPlugin{static manifest={incompatibleWith:[{name:"tree",reason:"Both plugins transform the entire row model. TreePlugin flattens nested hierarchies while GroupingRowsPlugin groups flat rows with synthetic headers. Use one approach per grid."},{name:"pivot",reason:"PivotPlugin creates its own aggregated row and column structure. Row grouping cannot be applied on top of pivot-generated rows."},{name:"serverSide",reason:"Row grouping requires the full dataset to compute group boundaries. ServerSidePlugin lazy-loads rows in blocks, so groups cannot be built client-side."}],events:[{type:"grouping-state-change",description:"Emitted when groups are expanded/collapsed. Subscribers can react to row visibility changes."}],queries:[{type:"canMoveRow",description:"Returns false for group header rows (cannot be reordered)"}],configRules:[{id:"groupingRows/accordion-defaultExpanded",severity:"warn",message:'"accordion: true" and "defaultExpanded" (non-false) are used together.\n → In accordion mode, only one group can be open at a time.\n → Using defaultExpanded with multiple groups will collapse to one on first toggle.\n → Consider using "defaultExpanded: false" or a single group key/index with accordion mode.',check:e=>!0===e.accordion&&!1!==e.defaultExpanded&&void 0!==e.defaultExpanded&&!("number"==typeof e.defaultExpanded)&&!("string"==typeof e.defaultExpanded)&&(!0===e.defaultExpanded||Array.isArray(e.defaultExpanded)&&e.defaultExpanded.length>1)}]};name="groupingRows";styles="@layer tbw-plugins{.group-row{display:grid;grid-template-columns:var(--tbw-column-template);background:var(--tbw-grouping-rows-bg, var(--tbw-color-panel-bg));font-weight:500;border-bottom:var(--tbw-row-divider);min-height:var(--tbw-row-height)}.group-row .cell{display:flex;align-items:center;padding:var(--tbw-cell-padding, .125rem .5rem)}@media(hover:hover){.group-row:hover{background:var(--tbw-grouping-rows-bg-hover, var(--tbw-color-row-hover))}}.group-toggle{cursor:pointer;-webkit-user-select:none;user-select:none;display:inline-flex;align-items:center;justify-content:center;width:var(--tbw-toggle-size, 1.25rem);height:var(--tbw-toggle-size, 1.25rem);margin-right:.25rem;background:none;border:0;font:inherit}.group-toggle:hover{background:var(--tbw-grouping-rows-toggle-hover, var(--tbw-color-row-hover));border-radius:var(--tbw-border-radius, .125rem)}.group-label{display:inline-flex;align-items:center;gap:var(--tbw-panel-gap, var(--tbw-spacing-md, .5rem))}.group-count{color:var(--tbw-grouping-rows-count-color, var(--tbw-color-fg-muted));font-size:var(--tbw-font-size-xs, .85em);font-weight:400}.group-aggregates{display:inline-flex;align-items:center;gap:var(--tbw-spacing-lg, 1rem);margin-left:var(--tbw-spacing-lg, 1rem);font-weight:400;font-size:var(--tbw-font-size-sm, .875em);color:var(--tbw-grouping-rows-aggregate-color, var(--tbw-color-fg-muted))}.group-aggregate{white-space:nowrap}.group-row{padding-left:calc(var(--tbw-group-depth, 0) * var(--tbw-group-indent-width, 1.25em))}.data-grid-row.tbw-group-slide-in{animation:tbw-group-slide-in var(--tbw-animation-duration, .2s) var(--tbw-animation-easing, ease-out) forwards}@keyframes tbw-group-slide-in{0%{opacity:0;transform:translate(-8px)}to{opacity:1;transform:translate(0)}}.data-grid-row.tbw-group-fade-in{animation:tbw-group-fade-in var(--tbw-animation-duration, .2s) var(--tbw-animation-easing, ease-out) forwards}@keyframes tbw-group-fade-in{0%{opacity:0}to{opacity:1}}}";get defaultConfig(){return{defaultExpanded:!1,showRowCount:!0,indentWidth:20,aggregators:{},animation:"slide",accordion:!1}}expandedKeys=new Set;flattenedRows=[];isActive=!1;previousVisibleKeys=new Set;keysToAnimate=new Set;hasAppliedDefaultExpanded=!1;get animationStyle(){return!!this.isAnimationEnabled&&(this.config.animation??"slide")}detach(){this.expandedKeys.clear(),this.flattenedRows=[],this.isActive=!1,this.previousVisibleKeys.clear(),this.keysToAnimate.clear(),this.hasAppliedDefaultExpanded=!1}getRowHeight(e,t){if(null!=this.config.groupRowHeight)return!0===e.__isGroupRow?this.config.groupRowHeight:void 0}handleQuery(e){if("canMoveRow"===e.type){const t=e.context;if(!0===t?.__isGroupRow)return!1}}static detect(e,t){return"function"==typeof t?.groupOn||"boolean"==typeof t?.enableRowGrouping}processRows(e){const t=this.config;if("function"!=typeof t.groupOn)return this.isActive=!1,this.flattenedRows=[],[...e];const n=s({rows:[...e],config:t,expanded:new Set});if(0===n.length)return this.isActive=!1,this.flattenedRows=[],[...e];let r;if(!this.hasAppliedDefaultExpanded&&0===this.expandedKeys.size&&!1!==t.defaultExpanded){const e=function(e){return e.filter(e=>"group"===e.kind).map(e=>e.key)}(n);r=function(e,t){if(!0===e)return new Set(t);if(!1===e||null==e)return new Set;if("number"==typeof e){const n=t[e];return n?new Set([n]):new Set}return"string"==typeof e?new Set([e]):Array.isArray(e)?new Set(e):new Set}(t.defaultExpanded??!1,e),r.size>0&&(this.expandedKeys=new Set(r),this.hasAppliedDefaultExpanded=!0)}const o=s({rows:[...e],config:t,expanded:this.expandedKeys,initialExpanded:r});this.isActive=!0,this.flattenedRows=o,this.keysToAnimate.clear();const i=new Set;return o.forEach((e,t)=>{if("data"===e.kind){const e=`data-${t}`;i.add(e),this.previousVisibleKeys.has(e)||this.keysToAnimate.add(e)}}),this.previousVisibleKeys=i,o.map(e=>{return"group"===e.kind?{__isGroupRow:!0,__groupKey:e.key,__groupValue:e.value,__groupDepth:e.depth,__groupRows:e.rows,__groupExpanded:e.expanded,__groupRowCount:(t=e,"group"!==t.kind?0:t.rows.length),__rowCacheKey:`group:${e.key}`}:e.row;var t})}onCellClick(e){const n=e.row;if(n?.__isGroupRow){const r=e.originalEvent.target;if(r?.closest(`.${t.GridClasses.GROUP_TOGGLE}`))return this.toggle(n.__groupKey),!0}}onKeyDown(e){if(" "!==e.key)return;const t=this.grid._focusRow,n=this.rows[t];return n?.__isGroupRow?(e.preventDefault(),this.toggle(n.__groupKey),this.requestRenderWithFocus(),!0):void 0}renderRow(e,t,n){if(!e?.__isGroupRow)return!1;const r=this.config;if(r.groupRowRenderer){const n=()=>{this.toggle(e.__groupKey)},o=r.groupRowRenderer({key:e.__groupKey,value:e.__groupValue,depth:e.__groupDepth,rows:e.__groupRows,expanded:e.__groupExpanded,toggleExpand:n});if(o)return t.className="data-grid-row group-row",t.__isCustomRow=!0,t.setAttribute("data-group-depth",String(e.__groupDepth)),"string"==typeof o?t.innerHTML=o:(t.innerHTML="",t.appendChild(o)),!0}const o=()=>{this.toggle(e.__groupKey)};t.className="data-grid-row group-row",t.__isCustomRow=!0,t.setAttribute("data-group-depth",String(e.__groupDepth)),t.setAttribute("role","row"),t.setAttribute("aria-expanded",String(e.__groupExpanded)),t.style.setProperty("--tbw-group-depth",String(e.__groupDepth||0)),void 0!==r.indentWidth&&t.style.setProperty("--tbw-group-indent-width",`${r.indentWidth}px`),t.style.height="",t.innerHTML="";return!1!==r.fullWidth?this.renderFullWidthGroupRow(e,t,o):this.renderPerColumnGroupRow(e,t,o),!0}afterRender(){const e=this.animationStyle;if(!1===e||0===this.keysToAnimate.size)return;const t=this.gridElement?.querySelector(".rows");if(!t)return;const n="fade"===e?"tbw-group-fade-in":"tbw-group-slide-in";for(const r of t.querySelectorAll(".data-grid-row:not(.group-row)")){const e=r.querySelector(".cell[data-row]"),t=e?parseInt(e.getAttribute("data-row")??"-1",10):-1,o=this.flattenedRows[t],i="data"===o?.kind?`data-${t}`:void 0;i&&this.keysToAnimate.has(i)&&(r.classList.add(n),r.addEventListener("animationend",()=>r.classList.remove(n),{once:!0}))}this.keysToAnimate.clear()}createToggleButton(e,n){const r=document.createElement("button");return r.type="button",r.className=`${t.GridClasses.GROUP_TOGGLE}${e?` ${t.GridClasses.EXPANDED}`:""}`,r.setAttribute("aria-label",e?"Collapse group":"Expand group"),this.setIcon(r,e?"collapse":"expand"),r.addEventListener("click",e=>{e.stopPropagation(),n()}),r}getGroupLabelText(e,t,n){const r=this.config;return r.formatLabel?r.formatLabel(e,t,n):String(e)}renderFullWidthGroupRow(e,r,o){const i=this.config,s=i.aggregators??{},a=e.__groupRows??[],d=document.createElement("div");d.className="cell group-full",d.style.gridColumn="1 / -1",d.setAttribute("role","gridcell"),d.setAttribute("data-col","0"),d.appendChild(this.createToggleButton(e.__groupExpanded,o));const l=document.createElement("span");if(l.className=t.GridClasses.GROUP_LABEL,l.textContent=this.getGroupLabelText(e.__groupValue,e.__groupDepth||0,e.__groupKey),d.appendChild(l),!1!==i.showRowCount){const n=document.createElement("span");n.className=t.GridClasses.GROUP_COUNT,n.textContent=`(${e.__groupRowCount??e.__groupRows?.length??0})`,d.appendChild(n)}const u=Object.entries(s);if(u.length>0){const e=document.createElement("span");e.className="group-aggregates";for(const[t,r]of u){const o=this.columns.find(e=>e.field===t),i=n.aggregatorRegistry.run(r,a,t,o);if(null!=i){const n=document.createElement("span");n.className="group-aggregate",n.setAttribute("data-field",t);const r=o?.header??t;n.textContent=`${r}: ${i}`,e.appendChild(n)}}e.children.length>0&&d.appendChild(e)}r.appendChild(d)}renderPerColumnGroupRow(e,r,o){const s=this.config,a=s.aggregators??{},d=this.columns,l=e.__groupRows??[],u=this.gridElement?.querySelector(".body"),p=u?.style.gridTemplateColumns||"";p&&(r.style.display="grid",r.style.gridTemplateColumns=p);let g=!1;d.forEach((d,u)=>{const p=document.createElement("div");if(p.className="cell group-cell",p.setAttribute("data-col",String(u)),p.setAttribute("role","gridcell"),i.isExpanderColumn(d))return p.setAttribute("data-field",d.field),void r.appendChild(p);if(g){const e=a[d.field];if(e){const t=n.aggregatorRegistry.run(e,l,d.field,d);p.textContent=null!=t?String(t):""}else p.textContent=""}else{g=!0,p.appendChild(this.createToggleButton(e.__groupExpanded,o));const r=document.createElement("span"),i=a[d.field];if(i){const t=n.aggregatorRegistry.run(i,l,d.field,d);r.textContent=String(null!=t?t:e.__groupValue)}else r.textContent=this.getGroupLabelText(e.__groupValue,e.__groupDepth||0,e.__groupKey);if(p.appendChild(r),!1!==s.showRowCount){const e=document.createElement("span");e.className=t.GridClasses.GROUP_COUNT,e.textContent=` (${l.length})`,p.appendChild(e)}}r.appendChild(p)})}expandAll(){this.expandedKeys=function(e){const t=new Set;for(const n of e)"group"===n.kind&&t.add(n.key);return t}(this.flattenedRows),this.emitPluginEvent("grouping-state-change",{expandedKeys:[...this.expandedKeys]}),this.requestRender()}collapseAll(){this.expandedKeys=new Set,this.emitPluginEvent("grouping-state-change",{expandedKeys:[...this.expandedKeys]}),this.requestRender()}toggle(e){const t=!this.expandedKeys.has(e),n=this.config,o=this.flattenedRows.find(t=>"group"===t.kind&&t.key===e);if(n.accordion&&t&&o){const t=new Set;for(const n of this.expandedKeys)if(e.startsWith(n+"||")||n.startsWith(e+"||"))e.startsWith(n+"||")&&t.add(n);else{const e=this.flattenedRows.find(e=>"group"===e.kind&&e.key===n);e&&e.depth!==o.depth&&t.add(n)}t.add(e),this.expandedKeys=t}else this.expandedKeys=function(e,t){const n=new Set(e);return n.has(t)?n.delete(t):n.add(t),n}(this.expandedKeys,e);this.emit("group-toggle",{key:e,expanded:this.expandedKeys.has(e),value:o?.value,depth:o?.depth??0});const i=this.expandedKeys.has(e),s=null!=o?.value?String(o.value):e;if(i){const e=o?.rows?.length??0;r.announce(this.gridElement,r.getA11yMessage(this.gridElement,"groupExpanded",s,e))}else r.announce(this.gridElement,r.getA11yMessage(this.gridElement,"groupCollapsed",s));this.emitPluginEvent("grouping-state-change",{expandedKeys:[...this.expandedKeys]}),this.requestRender()}isExpanded(e){return this.expandedKeys.has(e)}expand(e){this.expandedKeys.has(e)||(this.expandedKeys=new Set([...this.expandedKeys,e]),this.requestRender())}collapse(e){if(this.expandedKeys.has(e)){const t=new Set(this.expandedKeys);t.delete(e),this.expandedKeys=t,this.requestRender()}}getGroupState(){const e=this.flattenedRows.filter(e=>"group"===e.kind);return{isActive:this.isActive,expandedCount:this.expandedKeys.size,totalGroups:e.length,expandedKeys:[...this.expandedKeys]}}getRowCount(){return this.flattenedRows.length}refreshGroups(){this.requestRender()}getExpandedGroups(){return[...this.expandedKeys]}getFlattenedRows(){return this.flattenedRows}isGroupingActive(){return this.isActive}setGroupOn(e){this.config.groupOn=e,this.requestRender()}}e.GroupingRowsPlugin=a,Object.defineProperty(e,Symbol.toStringTag,{value:"Module"})});
|
|
1
|
+
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("../../core/constants"),require("../../core/internal/aggregators"),require("../../core/internal/aria"),require("../../core/internal/loading"),require("../../core/plugin/base-plugin"),require("../../core/plugin/expander-column")):"function"==typeof define&&define.amd?define(["exports","../../core/constants","../../core/internal/aggregators","../../core/internal/aria","../../core/internal/loading","../../core/plugin/base-plugin","../../core/plugin/expander-column"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).TbwGridPlugin_groupingRows={},e.TbwGrid,e.TbwGrid,e.TbwGrid,e.TbwGrid,e.TbwGrid,e.TbwGrid)}(this,function(e,t,r,n,o,i,s){"use strict";function a({rows:e,config:t,expanded:r,initialExpanded:n}){const o=t.groupOn;if("function"!=typeof o)return[];const i={key:"__root__",value:null,depth:-1,rows:[],children:new Map};if(e.forEach(e=>{let t=o(e);null==t||!1===t?t=["__ungrouped__"]:Array.isArray(t)||(t=[t]);let r=i;t.forEach((t,n)=>{const o=null==t?"∅":String(t),i="__root__"===r.key?o:r.key+"||"+o;let s=r.children.get(o);s||(s={key:i,value:t,depth:n,rows:[],children:new Map,parent:r},r.children.set(o,s)),s.rows.push(e),r=s})}),1===i.children.size&&i.children.has("__ungrouped__")){if(i.children.get("__ungrouped__").rows.length===e.length)return[]}const s=new Map;for(let u=0;u<e.length;u++)s.set(e[u],u);const a=new Set([...r,...n??[]]),d=[],p=e=>{if(e===i)return void e.children.forEach(e=>p(e));const t=a.has(e.key);d.push({kind:"group",key:e.key,value:e.value,depth:e.depth,rows:e.rows,expanded:t}),t&&(e.children.size?e.children.forEach(e=>p(e)):e.rows.forEach(e=>d.push({kind:"data",row:e,rowIndex:s.get(e)??-1})))};return p(i),d}function d(e,t){if(!0===e)return new Set(t);if(!1===e||null==e)return new Set;if("number"==typeof e){const r=t[e];return r?new Set([r]):new Set}return"string"==typeof e?new Set([e]):Array.isArray(e)?new Set(e):new Set}function p({groups:e,expanded:t,groupRows:r,loadingGroups:n,parentPath:o=[]}){const i=[],s=o.length;for(const a of e){const e=[...o,a.key],d=t.has(a.key),u=r.get(a.key)??[],l=n.has(a.key);if(i.push({kind:"group",key:a.key,value:a.value,depth:s,rows:u,expanded:d}),d)if(a.children?.length){const o=p({groups:a.children,expanded:t,groupRows:r,loadingGroups:n,parentPath:e});i.push(...o)}else l?i.push({kind:"data",row:{__loading:!0,__groupKey:a.key},rowIndex:-1}):u.forEach((e,t)=>{i.push({kind:"data",row:e,rowIndex:t})})}return i}function u(e,t,r=[]){for(const n of e){const e=[...r,n.key];if(n.key===t)return e;if(n.children?.length){const r=u(n.children,t,e);if(r.length>0)return r}}return[]}class l extends i.BaseGridPlugin{static manifest={incompatibleWith:[{name:"tree",reason:"Both plugins transform the entire row model. TreePlugin flattens nested hierarchies while GroupingRowsPlugin groups flat rows with synthetic headers. Use one approach per grid."},{name:"pivot",reason:"PivotPlugin creates its own aggregated row and column structure. Row grouping cannot be applied on top of pivot-generated rows."}],events:[{type:"grouping-state-change",description:"Emitted when groups are expanded/collapsed. Subscribers can react to row visibility changes."},{type:"group-expand",description:"Emitted when a pre-defined group is expanded. Use to lazily load group row data."},{type:"group-collapse",description:"Emitted when a pre-defined group is collapsed."}],queries:[{type:"canMoveRow",description:"Returns false for group header rows (cannot be reordered)"}],configRules:[{id:"groupingRows/accordion-defaultExpanded",severity:"warn",message:'"accordion: true" and "defaultExpanded" (non-false) are used together.\n → In accordion mode, only one group can be open at a time.\n → Using defaultExpanded with multiple groups will collapse to one on first toggle.\n → Consider using "defaultExpanded: false" or a single group key/index with accordion mode.',check:e=>!0===e.accordion&&!1!==e.defaultExpanded&&void 0!==e.defaultExpanded&&!("number"==typeof e.defaultExpanded)&&!("string"==typeof e.defaultExpanded)&&(!0===e.defaultExpanded||Array.isArray(e.defaultExpanded)&&e.defaultExpanded.length>1)}]};name="groupingRows";styles="@layer tbw-plugins{.group-row{display:grid;grid-template-columns:var(--tbw-column-template);background:var(--tbw-grouping-rows-bg, var(--tbw-color-panel-bg));font-weight:500;border-bottom:var(--tbw-row-divider);min-height:var(--tbw-row-height)}.group-row .cell{display:flex;align-items:center;padding:var(--tbw-cell-padding, .125rem .5rem)}@media(hover:hover){.group-row:hover{background:var(--tbw-grouping-rows-bg-hover, var(--tbw-color-row-hover))}}.group-toggle{cursor:pointer;-webkit-user-select:none;user-select:none;display:inline-flex;align-items:center;justify-content:center;width:var(--tbw-toggle-size, 1.25rem);height:var(--tbw-toggle-size, 1.25rem);margin-right:.25rem;background:none;border:0;font:inherit}.group-toggle:hover{background:var(--tbw-grouping-rows-toggle-hover, var(--tbw-color-row-hover));border-radius:var(--tbw-border-radius, .125rem)}.group-label{display:inline-flex;align-items:center;gap:var(--tbw-panel-gap, var(--tbw-spacing-md, .5rem))}.group-count{color:var(--tbw-grouping-rows-count-color, var(--tbw-color-fg-muted));font-size:var(--tbw-font-size-xs, .85em);font-weight:400}.group-aggregates{display:inline-flex;align-items:center;gap:var(--tbw-spacing-lg, 1rem);margin-left:var(--tbw-spacing-lg, 1rem);font-weight:400;font-size:var(--tbw-font-size-sm, .875em);color:var(--tbw-grouping-rows-aggregate-color, var(--tbw-color-fg-muted))}.group-aggregate{white-space:nowrap}.group-row{padding-left:calc(var(--tbw-group-depth, 0) * var(--tbw-group-indent-width, 1.25em))}.data-grid-row.tbw-group-slide-in{animation:tbw-group-slide-in var(--tbw-animation-duration, .2s) var(--tbw-animation-easing, ease-out) forwards}@keyframes tbw-group-slide-in{0%{opacity:0;transform:translate(-8px)}to{opacity:1;transform:translate(0)}}.data-grid-row.tbw-group-fade-in{animation:tbw-group-fade-in var(--tbw-animation-duration, .2s) var(--tbw-animation-easing, ease-out) forwards}@keyframes tbw-group-fade-in{0%{opacity:0}to{opacity:1}}}";get defaultConfig(){return{defaultExpanded:!1,showRowCount:!0,indentWidth:20,aggregators:{},animation:"slide",accordion:!1}}expandedKeys=new Set;flattenedRows=[];isActive=!1;previousVisibleKeys=new Set;keysToAnimate=new Set;hasAppliedDefaultExpanded=!1;preDefinedGroups=[];groupRowsMap=new Map;loadingGroups=new Set;groupsFetchInFlight=!1;rowsFetchInFlight=new Set;get animationStyle(){return!!this.isAnimationEnabled&&(this.config.animation??"slide")}detach(){this.expandedKeys.clear(),this.flattenedRows=[],this.isActive=!1,this.previousVisibleKeys.clear(),this.keysToAnimate.clear(),this.hasAppliedDefaultExpanded=!1,this.preDefinedGroups=[],this.groupRowsMap.clear(),this.loadingGroups.clear(),this.groupsFetchInFlight=!1,this.rowsFetchInFlight.clear()}getRowHeight(e,t){if(null!=this.config.groupRowHeight)return!0===e.__isGroupRow?this.config.groupRowHeight:void 0}handleQuery(e){if("canMoveRow"===e.type){const t=e.context;if(!0===t?.__isGroupRow)return!1}}static detect(e,t){return"function"==typeof t?.groupOn||"boolean"==typeof t?.enableRowGrouping||Array.isArray(t?.groups)||"function"==typeof t?.groups}processRows(e){if(this.preDefinedGroups.length>0||null!=this.config.groups)return"function"!=typeof this.config.groups||0!==this.preDefinedGroups.length||this.groupsFetchInFlight?this.preDefinedGroups.length>0||Array.isArray(this.config.groups)&&this.config.groups.length>0?this.processPreDefinedGroups():(this.isActive="function"==typeof this.config.groups,this.flattenedRows=[],[]):(this.fetchGroupsAsync(this.config.groups),this.isActive=!0,this.flattenedRows=[],[]);const t=this.config;if("function"!=typeof t.groupOn)return this.isActive=!1,this.flattenedRows=[],[...e];const r=a({rows:[...e],config:t,expanded:new Set});if(0===r.length)return this.isActive=!1,this.flattenedRows=[],[...e];let n;if(!this.hasAppliedDefaultExpanded&&0===this.expandedKeys.size&&!1!==t.defaultExpanded){const e=function(e){return e.filter(e=>"group"===e.kind).map(e=>e.key)}(r);n=d(t.defaultExpanded??!1,e),n.size>0&&(this.expandedKeys=new Set(n),this.hasAppliedDefaultExpanded=!0)}const o=a({rows:[...e],config:t,expanded:this.expandedKeys,initialExpanded:n});this.isActive=!0,this.flattenedRows=o,this.keysToAnimate.clear();const i=new Set;return o.forEach((e,t)=>{if("data"===e.kind){const e=`data-${t}`;i.add(e),this.previousVisibleKeys.has(e)||this.keysToAnimate.add(e)}}),this.previousVisibleKeys=i,o.map(e=>{return"group"===e.kind?{__isGroupRow:!0,__groupKey:e.key,__groupValue:e.value,__groupDepth:e.depth,__groupRows:e.rows,__groupExpanded:e.expanded,__groupRowCount:(t=e,"group"!==t.kind?0:t.rows.length),__rowCacheKey:`group:${e.key}`}:e.row;var t})}onCellClick(e){const r=e.row;if(r?.__isGroupRow){const n=e.originalEvent.target;if(n?.closest(`.${t.GridClasses.GROUP_TOGGLE}`))return this.toggle(r.__groupKey),!0}}onKeyDown(e){if(" "!==e.key)return;const t=this.grid._focusRow,r=this.rows[t];return r?.__isGroupRow?(e.preventDefault(),this.toggle(r.__groupKey),this.requestRenderWithFocus(),!0):void 0}renderRow(e,t,r){if(!0===e?.__loading&&e?.__groupKey){t.className="data-grid-row",t.__isCustomRow=!0,t.innerHTML="";const e=document.createElement("div");return e.className="cell",e.style.gridColumn="1 / -1",e.setAttribute("role","gridcell"),e.textContent=" ",t.appendChild(e),o.setRowLoadingState(t,!0),!0}if(!e?.__isGroupRow)return!1;const n=this.config;if(n.groupRowRenderer){const r=()=>{this.toggle(e.__groupKey)},o=n.groupRowRenderer({key:e.__groupKey,value:e.__groupValue,depth:e.__groupDepth,rows:e.__groupRows,expanded:e.__groupExpanded,toggleExpand:r});if(o)return t.className="data-grid-row group-row",t.__isCustomRow=!0,t.setAttribute("data-group-depth",String(e.__groupDepth)),"string"==typeof o?t.innerHTML=o:(t.innerHTML="",t.appendChild(o)),!0}const i=()=>{this.toggle(e.__groupKey)};t.className="data-grid-row group-row",t.__isCustomRow=!0,t.setAttribute("data-group-depth",String(e.__groupDepth)),t.setAttribute("role","row"),t.setAttribute("aria-expanded",String(e.__groupExpanded)),t.style.setProperty("--tbw-group-depth",String(e.__groupDepth||0)),void 0!==n.indentWidth&&t.style.setProperty("--tbw-group-indent-width",`${n.indentWidth}px`),t.style.height="",t.innerHTML="";return!1!==n.fullWidth?this.renderFullWidthGroupRow(e,t,i):this.renderPerColumnGroupRow(e,t,i),!0}afterRender(){const e=this.animationStyle;if(!1===e||0===this.keysToAnimate.size)return;const t=this.gridElement?.querySelector(".rows");if(!t)return;const r="fade"===e?"tbw-group-fade-in":"tbw-group-slide-in";for(const n of t.querySelectorAll(".data-grid-row:not(.group-row)")){const e=n.querySelector(".cell[data-row]"),t=e?parseInt(e.getAttribute("data-row")??"-1",10):-1,o=this.flattenedRows[t],i="data"===o?.kind?`data-${t}`:void 0;i&&this.keysToAnimate.has(i)&&(n.classList.add(r),n.addEventListener("animationend",()=>n.classList.remove(r),{once:!0}))}this.keysToAnimate.clear()}processPreDefinedGroups(){const e=this.preDefinedGroups.length>0?this.preDefinedGroups:Array.isArray(this.config.groups)?this.config.groups:[];if(0===e.length)return this.isActive=!1,this.flattenedRows=[],[];if(!this.hasAppliedDefaultExpanded&&0===this.expandedKeys.size&&!1!==this.config.defaultExpanded){const t=this.collectGroupKeys(e),r=d(this.config.defaultExpanded??!1,t);r.size>0&&(this.expandedKeys=new Set(r),this.hasAppliedDefaultExpanded=!0)}const t=p({groups:e,expanded:this.expandedKeys,groupRows:this.groupRowsMap,loadingGroups:this.loadingGroups});this.isActive=!0,this.flattenedRows=t,this.keysToAnimate.clear();const r=new Set;return t.forEach((e,t)=>{if("data"===e.kind){const e=`data-${t}`;r.add(e),this.previousVisibleKeys.has(e)||this.keysToAnimate.add(e)}}),this.previousVisibleKeys=r,t.map(t=>{if("group"===t.kind){const r=this.findGroupDefinition(e,t.key),n=r?.rowCount??t.rows.length;return{__isGroupRow:!0,__groupKey:t.key,__groupValue:t.value,__groupDepth:t.depth,__groupRows:t.rows,__groupExpanded:t.expanded,__groupRowCount:n,__rowCacheKey:`group:${t.key}`}}return t.row})}fetchGroupsAsync(e){this.groupsFetchInFlight=!0,e().then(e=>{this.groupsFetchInFlight=!1,this.preDefinedGroups=e,this.requestRender()},()=>{this.groupsFetchInFlight=!1})}fetchGroupRowsAsync(e,t){const r=this.config.rows;r&&!this.rowsFetchInFlight.has(e)&&(this.rowsFetchInFlight.add(e),this.loadingGroups.add(e),this.requestRender(),r(t).then(t=>{this.rowsFetchInFlight.delete(e),this.groupRowsMap.set(e,t),this.loadingGroups.delete(e),this.requestRender()},()=>{this.rowsFetchInFlight.delete(e),this.loadingGroups.delete(e),this.requestRender()}))}collectGroupKeys(e){const t=[];for(const r of e)t.push(r.key),r.children?.length&&t.push(...this.collectGroupKeys(r.children));return t}getActiveGroups(){return this.preDefinedGroups.length>0?this.preDefinedGroups:Array.isArray(this.config.groups)?this.config.groups:[]}findGroupDefinition(e,t){for(const r of e){if(r.key===t)return r;if(r.children?.length){const e=this.findGroupDefinition(r.children,t);if(e)return e}}}createToggleButton(e,r){const n=document.createElement("button");return n.type="button",n.className=`${t.GridClasses.GROUP_TOGGLE}${e?` ${t.GridClasses.EXPANDED}`:""}`,n.setAttribute("aria-label",e?"Collapse group":"Expand group"),this.setIcon(n,e?"collapse":"expand"),n.addEventListener("click",e=>{e.stopPropagation(),r()}),n}getGroupLabelText(e,t,r){const n=this.config;return n.formatLabel?n.formatLabel(e,t,r):String(e)}renderFullWidthGroupRow(e,n,o){const i=this.config,s=i.aggregators??{},a=e.__groupRows??[],d=document.createElement("div");d.className="cell group-full",d.style.gridColumn="1 / -1",d.setAttribute("role","gridcell"),d.setAttribute("data-col","0"),d.appendChild(this.createToggleButton(e.__groupExpanded,o));const p=document.createElement("span");if(p.className=t.GridClasses.GROUP_LABEL,p.textContent=this.getGroupLabelText(e.__groupValue,e.__groupDepth||0,e.__groupKey),d.appendChild(p),!1!==i.showRowCount){const r=document.createElement("span");r.className=t.GridClasses.GROUP_COUNT,r.textContent=`(${e.__groupRowCount??e.__groupRows?.length??0})`,d.appendChild(r)}const u=Object.entries(s);if(u.length>0){const e=document.createElement("span");e.className="group-aggregates";for(const[t,n]of u){const o=this.columns.find(e=>e.field===t),i=r.aggregatorRegistry.run(n,a,t,o);if(null!=i){const r=document.createElement("span");r.className="group-aggregate",r.setAttribute("data-field",t);const n=o?.header??t;r.textContent=`${n}: ${i}`,e.appendChild(r)}}e.children.length>0&&d.appendChild(e)}n.appendChild(d)}renderPerColumnGroupRow(e,n,o){const i=this.config,a=i.aggregators??{},d=this.columns,p=e.__groupRows??[],u=this.gridElement?.querySelector(".body"),l=u?.style.gridTemplateColumns||"";l&&(n.style.display="grid",n.style.gridTemplateColumns=l);let g=!1;d.forEach((d,u)=>{const l=document.createElement("div");if(l.className="cell group-cell",l.setAttribute("data-col",String(u)),l.setAttribute("role","gridcell"),s.isExpanderColumn(d))return l.setAttribute("data-field",d.field),void n.appendChild(l);if(g){const e=a[d.field];if(e){const t=r.aggregatorRegistry.run(e,p,d.field,d);l.textContent=null!=t?String(t):""}else l.textContent=""}else{g=!0,l.appendChild(this.createToggleButton(e.__groupExpanded,o));const n=document.createElement("span"),s=a[d.field];if(s){const t=r.aggregatorRegistry.run(s,p,d.field,d);n.textContent=String(null!=t?t:e.__groupValue)}else n.textContent=this.getGroupLabelText(e.__groupValue,e.__groupDepth||0,e.__groupKey);if(l.appendChild(n),!1!==i.showRowCount){const e=document.createElement("span");e.className=t.GridClasses.GROUP_COUNT,e.textContent=` (${p.length})`,l.appendChild(e)}}n.appendChild(l)})}expandAll(){this.expandedKeys=function(e){const t=new Set;for(const r of e)"group"===r.kind&&t.add(r.key);return t}(this.flattenedRows),this.emitPluginEvent("grouping-state-change",{expandedKeys:[...this.expandedKeys]}),this.requestRender()}collapseAll(){this.expandedKeys=new Set,this.emitPluginEvent("grouping-state-change",{expandedKeys:[...this.expandedKeys]}),this.requestRender()}toggle(e){const t=!this.expandedKeys.has(e),r=this.config,o=this.flattenedRows.find(t=>"group"===t.kind&&t.key===e);if(r.accordion&&t&&o){const t=new Set;for(const r of this.expandedKeys)if(e.startsWith(r+"||")||r.startsWith(e+"||"))e.startsWith(r+"||")&&t.add(r);else{const e=this.flattenedRows.find(e=>"group"===e.kind&&e.key===r);e&&e.depth!==o.depth&&t.add(r)}t.add(e),this.expandedKeys=t}else this.expandedKeys=function(e,t){const r=new Set(e);return r.has(t)?r.delete(t):r.add(t),r}(this.expandedKeys,e);this.emit("group-toggle",{key:e,expanded:this.expandedKeys.has(e),value:o?.value,depth:o?.depth??0});const i=this.getActiveGroups();if(i.length>0){const n=u(i,e);if(t){if(this.emit("group-expand",{groupKey:e,groupPath:n}),r.rows&&!this.groupRowsMap.has(e)){const t=this.findGroupDefinition(i,e);t&&this.fetchGroupRowsAsync(e,t)}}else this.emit("group-collapse",{groupKey:e,groupPath:n})}const s=this.expandedKeys.has(e),a=null!=o?.value?String(o.value):e;if(s){const e=o?.rows?.length??0;n.announce(this.gridElement,n.getA11yMessage(this.gridElement,"groupExpanded",a,e))}else n.announce(this.gridElement,n.getA11yMessage(this.gridElement,"groupCollapsed",a));this.emitPluginEvent("grouping-state-change",{expandedKeys:[...this.expandedKeys]}),this.requestRender()}isExpanded(e){return this.expandedKeys.has(e)}expand(e){this.expandedKeys.has(e)||(this.expandedKeys=new Set([...this.expandedKeys,e]),this.requestRender())}collapse(e){if(this.expandedKeys.has(e)){const t=new Set(this.expandedKeys);t.delete(e),this.expandedKeys=t,this.requestRender()}}getGroupState(){const e=this.flattenedRows.filter(e=>"group"===e.kind);return{isActive:this.isActive,expandedCount:this.expandedKeys.size,totalGroups:e.length,expandedKeys:[...this.expandedKeys]}}getRowCount(){return this.flattenedRows.length}refreshGroups(){this.requestRender()}getExpandedGroups(){return[...this.expandedKeys]}getFlattenedRows(){return this.flattenedRows}isGroupingActive(){return this.isActive}setGroupOn(e){this.config.groupOn=e,this.requestRender()}setGroups(e){this.preDefinedGroups=e,this.groupRowsMap.clear(),this.loadingGroups.clear(),this.rowsFetchInFlight.clear(),this.expandedKeys.clear(),this.hasAppliedDefaultExpanded=!1,this.requestRender()}getGroups(){return this.preDefinedGroups.length>0?[...this.preDefinedGroups]:Array.isArray(this.config.groups)?[...this.config.groups]:[]}setGroupRows(e,t){this.groupRowsMap.set(e,t),this.loadingGroups.delete(e),this.requestRender()}setGroupLoading(e,t){t?this.loadingGroups.add(e):this.loadingGroups.delete(e),this.requestRender()}clearGroupRows(e){null!=e?(this.groupRowsMap.delete(e),this.rowsFetchInFlight.delete(e)):(this.groupRowsMap.clear(),this.rowsFetchInFlight.clear()),this.requestRender()}}e.GroupingRowsPlugin=l,Object.defineProperty(e,Symbol.toStringTag,{value:"Module"})});
|
|
2
2
|
//# sourceMappingURL=grouping-rows.umd.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"grouping-rows.umd.js","sources":["../../../../../libs/grid/src/lib/plugins/grouping-rows/grouping-rows.ts","../../../../../libs/grid/src/lib/plugins/grouping-rows/GroupingRowsPlugin.ts"],"sourcesContent":["/**\n * Row Grouping Core Logic\n *\n * Pure functions for building grouped row models and aggregations.\n */\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n\nimport type { DefaultExpandedValue, GroupingRowsConfig, GroupRowModelItem, RenderRow } from './types';\n\ninterface GroupNode {\n key: string; // composite key\n value: any;\n depth: number;\n rows: any[];\n children: Map<string, GroupNode>;\n parent?: GroupNode;\n}\n\ninterface BuildGroupingArgs {\n rows: any[];\n config: GroupingRowsConfig;\n expanded: Set<string>;\n /** Initial expanded state to apply (processed by the plugin) */\n initialExpanded?: Set<string>;\n}\n\n/**\n * Build a flattened grouping projection (collapsed by default).\n * Returns empty array when groupOn not configured or all rows ungrouped.\n *\n * @param args - The grouping arguments\n * @returns Flattened array of render rows (groups + data rows)\n */\nexport function buildGroupedRowModel({ rows, config, expanded, initialExpanded }: BuildGroupingArgs): RenderRow[] {\n const groupOn = config.groupOn;\n if (typeof groupOn !== 'function') {\n return [];\n }\n\n const root: GroupNode = { key: '__root__', value: null, depth: -1, rows: [], children: new Map() };\n\n // Build tree structure — push each row into every ancestor along the path\n // so that each group's `rows` array contains ALL data rows in its subtree.\n // This is required for correct counts and aggregations on multi-level groups.\n rows.forEach((r) => {\n let path: any = groupOn(r);\n if (path == null || path === false) path = ['__ungrouped__'];\n else if (!Array.isArray(path)) path = [path];\n\n let parent = root;\n path.forEach((rawVal: any, depthIdx: number) => {\n const seg = rawVal == null ? '∅' : String(rawVal);\n const composite = parent.key === '__root__' ? seg : parent.key + '||' + seg;\n let node = parent.children.get(seg);\n if (!node) {\n node = { key: composite, value: rawVal, depth: depthIdx, rows: [], children: new Map(), parent };\n parent.children.set(seg, node);\n }\n node.rows.push(r);\n parent = node;\n });\n });\n\n // All ungrouped? treat as no grouping\n if (root.children.size === 1 && root.children.has('__ungrouped__')) {\n const only = root.children.get('__ungrouped__')!;\n if (only.rows.length === rows.length) return [];\n }\n\n // Pre-build row→index map for O(1) lookups (avoids O(n²) from rows.indexOf)\n const rowIndexMap = new Map<any, number>();\n for (let i = 0; i < rows.length; i++) {\n rowIndexMap.set(rows[i], i);\n }\n\n // Merge expanded sets - use initialExpanded on first render, then expanded takes over\n const effectiveExpanded = new Set([...expanded, ...(initialExpanded ?? [])]);\n\n // Flatten tree to array\n const flat: RenderRow[] = [];\n const visit = (node: GroupNode) => {\n if (node === root) {\n node.children.forEach((c) => visit(c));\n return;\n }\n\n const isExpanded = effectiveExpanded.has(node.key);\n flat.push({\n kind: 'group',\n key: node.key,\n value: node.value,\n depth: node.depth,\n rows: node.rows,\n expanded: isExpanded,\n });\n\n if (isExpanded) {\n if (node.children.size) {\n node.children.forEach((c) => visit(c));\n } else {\n node.rows.forEach((r) => flat.push({ kind: 'data', row: r, rowIndex: rowIndexMap.get(r) ?? -1 }));\n }\n }\n };\n visit(root);\n\n return flat;\n}\n\n/**\n * Toggle expansion state for a group key.\n *\n * @param expandedKeys - Current set of expanded keys\n * @param key - The group key to toggle\n * @returns New set with toggled state\n */\nexport function toggleGroupExpansion(expandedKeys: Set<string>, key: string): Set<string> {\n const newSet = new Set(expandedKeys);\n if (newSet.has(key)) {\n newSet.delete(key);\n } else {\n newSet.add(key);\n }\n return newSet;\n}\n\n/**\n * Expand all groups.\n *\n * @param rows - The flattened render rows\n * @returns Set of all group keys\n */\nexport function expandAllGroups(rows: RenderRow[]): Set<string> {\n const keys = new Set<string>();\n for (const row of rows) {\n if (row.kind === 'group') {\n keys.add(row.key);\n }\n }\n return keys;\n}\n\n/**\n * Collapse all groups.\n *\n * @returns Empty set\n */\nexport function collapseAllGroups(): Set<string> {\n return new Set();\n}\n\n/**\n * Resolve a defaultExpanded value to a set of keys to expand.\n * This needs to be called AFTER building the group model to get all keys.\n *\n * @param value - The defaultExpanded config value\n * @param allGroupKeys - All group keys from the model\n * @returns Set of keys to expand initially\n */\nexport function resolveDefaultExpanded(value: DefaultExpandedValue, allGroupKeys: string[]): Set<string> {\n if (value === true) {\n // Expand all groups\n return new Set(allGroupKeys);\n }\n if (value === false || value == null) {\n // Collapse all groups\n return new Set();\n }\n if (typeof value === 'number') {\n // Expand group at this index\n const key = allGroupKeys[value];\n return key ? new Set([key]) : new Set();\n }\n if (typeof value === 'string') {\n // Expand group with this key\n return new Set([value]);\n }\n if (Array.isArray(value)) {\n // Expand groups with these keys\n return new Set(value);\n }\n return new Set();\n}\n\n/**\n * Get all group keys from a flattened model.\n *\n * @param rows - The flattened render rows\n * @returns Array of group keys\n */\nexport function getGroupKeys(rows: RenderRow[]): string[] {\n return rows.filter((r): r is GroupRowModelItem => r.kind === 'group').map((r) => r.key);\n}\n\n/**\n * Count total rows in a group (including nested groups).\n *\n * @param groupRow - The group row\n * @returns Total row count\n */\nexport function getGroupRowCount(groupRow: RenderRow): number {\n if (groupRow.kind !== 'group') return 0;\n return groupRow.rows.length;\n}\n","/**\n * Row Grouping Plugin (Class-based)\n *\n * Enables hierarchical row grouping with expand/collapse and aggregations.\n */\n\nimport { GridClasses } from '../../core/constants';\nimport { aggregatorRegistry } from '../../core/internal/aggregators';\nimport { announce, getA11yMessage } from '../../core/internal/aria';\nimport { BaseGridPlugin, CellClickEvent, type PluginManifest, type PluginQuery } from '../../core/plugin/base-plugin';\nimport { isExpanderColumn } from '../../core/plugin/expander-column';\nimport type { RowElementInternal } from '../../core/types';\nimport {\n buildGroupedRowModel,\n collapseAllGroups,\n expandAllGroups,\n getGroupKeys,\n getGroupRowCount,\n resolveDefaultExpanded,\n toggleGroupExpansion,\n} from './grouping-rows';\nimport styles from './grouping-rows.css?inline';\nimport type {\n ExpandCollapseAnimation,\n GroupingRowsConfig,\n GroupRowModelItem,\n GroupToggleDetail,\n RenderRow,\n} from './types';\n\n/**\n * Group state information returned by getGroupState()\n */\nexport interface GroupState {\n /** Whether grouping is currently active */\n isActive: boolean;\n /** Number of expanded groups */\n expandedCount: number;\n /** Total number of groups */\n totalGroups: number;\n /** Array of expanded group keys */\n expandedKeys: string[];\n}\n\n/**\n * Row Grouping Plugin for tbw-grid\n *\n * Organizes rows into collapsible hierarchical groups. Perfect for organizing data\n * by category, department, status, or any other dimension—or even multiple dimensions\n * for nested grouping. Includes aggregation support for summarizing group data.\n *\n * ## Installation\n *\n * ```ts\n * import { GroupingRowsPlugin } from '@toolbox-web/grid/plugins/grouping-rows';\n * ```\n *\n * ## CSS Custom Properties\n *\n * | Property | Default | Description |\n * |----------|---------|-------------|\n * | `--tbw-group-indent-width` | `1.25em` | Indentation per group level |\n * | `--tbw-grouping-rows-bg` | `var(--tbw-color-panel-bg)` | Group row background |\n * | `--tbw-grouping-rows-count-color` | `var(--tbw-color-fg-muted)` | Count badge color |\n * | `--tbw-animation-duration` | `200ms` | Expand/collapse animation |\n *\n * @example Single-Level Grouping by Department\n * ```ts\n * import { queryGrid } from '@toolbox-web/grid';\n * import { GroupingRowsPlugin } from '@toolbox-web/grid/plugins/grouping-rows';\n *\n * const grid = queryGrid('tbw-grid');\n * grid.gridConfig = {\n * columns: [\n * { field: 'name', header: 'Employee' },\n * { field: 'department', header: 'Department' },\n * { field: 'salary', header: 'Salary', type: 'currency' },\n * ],\n * plugins: [\n * new GroupingRowsPlugin({\n * groupOn: (row) => [row.department],\n * showRowCount: true,\n * defaultExpanded: false,\n * }),\n * ],\n * };\n * ```\n *\n * @example Multi-Level Grouping\n * ```ts\n * new GroupingRowsPlugin({\n * groupOn: (row) => [row.region, row.department, row.team],\n * indentWidth: 24,\n * animation: 'slide',\n * })\n * ```\n *\n * @see {@link GroupingRowsConfig} for all configuration options\n * @see {@link GroupState} for the group state structure\n *\n * @internal Extends BaseGridPlugin\n */\nexport class GroupingRowsPlugin extends BaseGridPlugin<GroupingRowsConfig> {\n /**\n * Plugin manifest - declares configuration validation rules and events.\n * @internal\n */\n static override readonly manifest: PluginManifest<GroupingRowsConfig> = {\n incompatibleWith: [\n {\n name: 'tree',\n reason:\n 'Both plugins transform the entire row model. TreePlugin flattens nested hierarchies while ' +\n 'GroupingRowsPlugin groups flat rows with synthetic headers. Use one approach per grid.',\n },\n {\n name: 'pivot',\n reason:\n 'PivotPlugin creates its own aggregated row and column structure. ' +\n 'Row grouping cannot be applied on top of pivot-generated rows.',\n },\n {\n name: 'serverSide',\n reason:\n 'Row grouping requires the full dataset to compute group boundaries. ' +\n 'ServerSidePlugin lazy-loads rows in blocks, so groups cannot be built client-side.',\n },\n ],\n events: [\n {\n type: 'grouping-state-change',\n description: 'Emitted when groups are expanded/collapsed. Subscribers can react to row visibility changes.',\n },\n ],\n queries: [\n {\n type: 'canMoveRow',\n description: 'Returns false for group header rows (cannot be reordered)',\n },\n ],\n configRules: [\n {\n id: 'groupingRows/accordion-defaultExpanded',\n severity: 'warn',\n message:\n `\"accordion: true\" and \"defaultExpanded\" (non-false) are used together.\\n` +\n ` → In accordion mode, only one group can be open at a time.\\n` +\n ` → Using defaultExpanded with multiple groups will collapse to one on first toggle.\\n` +\n ` → Consider using \"defaultExpanded: false\" or a single group key/index with accordion mode.`,\n check: (config) =>\n config.accordion === true &&\n config.defaultExpanded !== false &&\n config.defaultExpanded !== undefined &&\n // Allow single group expansion with accordion\n !(typeof config.defaultExpanded === 'number') &&\n !(typeof config.defaultExpanded === 'string') &&\n // Warn if true or array with multiple items\n (config.defaultExpanded === true ||\n (Array.isArray(config.defaultExpanded) && config.defaultExpanded.length > 1)),\n },\n ],\n };\n\n /** @internal */\n readonly name = 'groupingRows';\n /** @internal */\n override readonly styles = styles;\n\n /** @internal */\n protected override get defaultConfig(): Partial<GroupingRowsConfig> {\n return {\n defaultExpanded: false,\n showRowCount: true,\n indentWidth: 20,\n aggregators: {},\n animation: 'slide',\n accordion: false,\n };\n }\n\n // #region Internal State\n private expandedKeys: Set<string> = new Set();\n private flattenedRows: RenderRow[] = [];\n private isActive = false;\n private previousVisibleKeys = new Set<string>();\n private keysToAnimate = new Set<string>();\n /** Track if initial defaultExpanded has been applied */\n private hasAppliedDefaultExpanded = false;\n // #endregion\n\n // #region Animation\n\n /**\n * Get expand/collapse animation style from plugin config.\n * Uses base class isAnimationEnabled to respect grid-level settings.\n */\n private get animationStyle(): ExpandCollapseAnimation {\n if (!this.isAnimationEnabled) return false;\n return this.config.animation ?? 'slide';\n }\n\n // #endregion\n\n // #region Lifecycle\n\n /** @internal */\n override detach(): void {\n this.expandedKeys.clear();\n this.flattenedRows = [];\n this.isActive = false;\n this.previousVisibleKeys.clear();\n this.keysToAnimate.clear();\n this.hasAppliedDefaultExpanded = false;\n }\n\n /**\n * Provide row height for group header rows.\n *\n * If `groupRowHeight` is configured, returns that value for group rows.\n * This allows the variable row height system to use known heights for\n * group headers without needing to measure them from the DOM.\n *\n * @param row - The row object (may be a group row)\n * @param _index - Index in the processed rows array (unused)\n * @returns Height in pixels for group rows, undefined for data rows\n *\n * @internal Plugin hook for variable row height support\n */\n override getRowHeight(row: unknown, _index: number): number | undefined {\n // Only provide height if groupRowHeight is configured\n if (this.config.groupRowHeight == null) return undefined;\n\n // Check if this is a group row\n if ((row as { __isGroupRow?: boolean }).__isGroupRow === true) {\n return this.config.groupRowHeight;\n }\n\n return undefined;\n }\n\n /**\n * Handle plugin queries.\n * @internal\n */\n override handleQuery(query: PluginQuery): unknown {\n if (query.type === 'canMoveRow') {\n // Group header rows cannot be reordered\n const row = query.context as { __isGroupRow?: boolean } | null | undefined;\n if (row?.__isGroupRow === true) {\n return false;\n }\n }\n return undefined;\n }\n // #endregion\n\n // #region Hooks\n\n /**\n * Auto-detect grouping configuration from grid config.\n * Called by plugin system to determine if plugin should activate.\n */\n static detect(rows: readonly any[], config: any): boolean {\n return typeof config?.groupOn === 'function' || typeof config?.enableRowGrouping === 'boolean';\n }\n\n /** @internal */\n override processRows(rows: readonly any[]): any[] {\n const config = this.config;\n\n // Check if grouping is configured\n if (typeof config.groupOn !== 'function') {\n this.isActive = false;\n this.flattenedRows = [];\n return [...rows];\n }\n\n // First build: get structure to know all group keys\n // (needed for index-based defaultExpanded)\n const initialBuild = buildGroupedRowModel({\n rows: [...rows],\n config: config,\n expanded: new Set(), // Empty to get all root groups\n });\n\n // If no grouping produced, return original rows\n if (initialBuild.length === 0) {\n this.isActive = false;\n this.flattenedRows = [];\n return [...rows];\n }\n\n // Resolve defaultExpanded on first render only\n let initialExpanded: Set<string> | undefined;\n if (!this.hasAppliedDefaultExpanded && this.expandedKeys.size === 0 && config.defaultExpanded !== false) {\n const allKeys = getGroupKeys(initialBuild);\n initialExpanded = resolveDefaultExpanded(config.defaultExpanded ?? false, allKeys);\n\n // Mark as applied and populate expandedKeys for subsequent toggles\n if (initialExpanded.size > 0) {\n this.expandedKeys = new Set(initialExpanded);\n this.hasAppliedDefaultExpanded = true;\n }\n }\n\n // Build with proper expanded state\n const grouped = buildGroupedRowModel({\n rows: [...rows],\n config: config,\n expanded: this.expandedKeys,\n initialExpanded,\n });\n\n this.isActive = true;\n this.flattenedRows = grouped;\n\n // Track which data rows are newly visible (for animation)\n this.keysToAnimate.clear();\n const currentVisibleKeys = new Set<string>();\n grouped.forEach((item, idx) => {\n if (item.kind === 'data') {\n const key = `data-${idx}`;\n currentVisibleKeys.add(key);\n if (!this.previousVisibleKeys.has(key)) {\n this.keysToAnimate.add(key);\n }\n }\n });\n this.previousVisibleKeys = currentVisibleKeys;\n\n // Return flattened rows for rendering\n // The grid will need to handle group rows specially\n return grouped.map((item) => {\n if (item.kind === 'group') {\n return {\n __isGroupRow: true,\n __groupKey: item.key,\n __groupValue: item.value,\n __groupDepth: item.depth,\n __groupRows: item.rows,\n __groupExpanded: item.expanded,\n __groupRowCount: getGroupRowCount(item),\n // Cache key for variable row height support - survives expand/collapse\n __rowCacheKey: `group:${item.key}`,\n };\n }\n return item.row;\n });\n }\n\n /** @internal */\n override onCellClick(event: CellClickEvent): boolean | void {\n const row = event.row as Record<string, unknown> | undefined;\n\n // Check if this is a group row toggle\n if (row?.__isGroupRow) {\n const target = event.originalEvent.target as HTMLElement;\n if (target?.closest(`.${GridClasses.GROUP_TOGGLE}`)) {\n this.toggle(row.__groupKey as string);\n return true; // Prevent default\n }\n }\n }\n\n /** @internal */\n override onKeyDown(event: KeyboardEvent): boolean | void {\n // SPACE toggles expansion on group rows\n if (event.key !== ' ') return;\n\n const focusRow = this.grid._focusRow;\n const row = this.rows[focusRow] as Record<string, unknown> | undefined;\n\n // Only handle SPACE on group rows\n if (!row?.__isGroupRow) return;\n\n event.preventDefault();\n this.toggle(row.__groupKey as string);\n\n // Restore focus styling after render completes via render pipeline\n this.requestRenderWithFocus();\n return true;\n }\n\n /**\n * Render a row. Returns true if we handled the row (group row), false otherwise.\n * @internal\n */\n override renderRow(row: any, rowEl: HTMLElement, _rowIndex: number): boolean {\n // Only handle group rows\n if (!row?.__isGroupRow) {\n return false;\n }\n\n const config = this.config;\n\n // If a custom renderer is provided, use it\n if (config.groupRowRenderer) {\n const toggleExpand = () => {\n this.toggle(row.__groupKey);\n };\n\n const result = config.groupRowRenderer({\n key: row.__groupKey,\n value: row.__groupValue,\n depth: row.__groupDepth,\n rows: row.__groupRows,\n expanded: row.__groupExpanded,\n toggleExpand,\n });\n\n if (result) {\n rowEl.className = 'data-grid-row group-row';\n (rowEl as RowElementInternal).__isCustomRow = true; // Mark for proper class reset on recycle\n rowEl.setAttribute('data-group-depth', String(row.__groupDepth));\n if (typeof result === 'string') {\n rowEl.innerHTML = result;\n } else {\n rowEl.innerHTML = '';\n rowEl.appendChild(result);\n }\n return true;\n }\n }\n\n // Helper to toggle expansion\n const handleToggle = () => {\n this.toggle(row.__groupKey);\n };\n\n // Default group row rendering - keep data-grid-row class for focus/keyboard navigation\n rowEl.className = 'data-grid-row group-row';\n (rowEl as RowElementInternal).__isCustomRow = true; // Mark for proper class reset on recycle\n rowEl.setAttribute('data-group-depth', String(row.__groupDepth));\n rowEl.setAttribute('role', 'row');\n rowEl.setAttribute('aria-expanded', String(row.__groupExpanded));\n // Use CSS variable for depth-based indentation\n rowEl.style.setProperty('--tbw-group-depth', String(row.__groupDepth || 0));\n if (config.indentWidth !== undefined) {\n rowEl.style.setProperty('--tbw-group-indent-width', `${config.indentWidth}px`);\n }\n // Clear any inline height from previous use (e.g., responsive card mode sets height: auto)\n // This ensures group rows use CSS-defined height, not stale inline styles from recycled elements\n rowEl.style.height = '';\n rowEl.innerHTML = '';\n\n const isFullWidth = config.fullWidth !== false; // default true\n\n if (isFullWidth) {\n this.renderFullWidthGroupRow(row, rowEl, handleToggle);\n } else {\n this.renderPerColumnGroupRow(row, rowEl, handleToggle);\n }\n\n return true;\n }\n\n /** @internal */\n override afterRender(): void {\n const style = this.animationStyle;\n if (style === false || this.keysToAnimate.size === 0) return;\n\n const body = this.gridElement?.querySelector('.rows');\n if (!body) return;\n\n const animClass = style === 'fade' ? 'tbw-group-fade-in' : 'tbw-group-slide-in';\n for (const rowEl of body.querySelectorAll('.data-grid-row:not(.group-row)')) {\n const cell = rowEl.querySelector('.cell[data-row]');\n const idx = cell ? parseInt(cell.getAttribute('data-row') ?? '-1', 10) : -1;\n const item = this.flattenedRows[idx];\n const key = item?.kind === 'data' ? `data-${idx}` : undefined;\n\n if (key && this.keysToAnimate.has(key)) {\n rowEl.classList.add(animClass);\n rowEl.addEventListener('animationend', () => rowEl.classList.remove(animClass), { once: true });\n }\n }\n this.keysToAnimate.clear();\n }\n // #endregion\n\n // #region Private Rendering Helpers\n\n /**\n * Create a toggle button for expanding/collapsing a group.\n */\n private createToggleButton(expanded: boolean, handleToggle: () => void): HTMLButtonElement {\n const btn = document.createElement('button');\n btn.type = 'button';\n btn.className = `${GridClasses.GROUP_TOGGLE}${expanded ? ` ${GridClasses.EXPANDED}` : ''}`;\n btn.setAttribute('aria-label', expanded ? 'Collapse group' : 'Expand group');\n this.setIcon(btn, expanded ? 'collapse' : 'expand');\n btn.addEventListener('click', (e) => {\n e.stopPropagation();\n handleToggle();\n });\n return btn;\n }\n\n /**\n * Get the formatted label text for a group.\n */\n private getGroupLabelText(value: unknown, depth: number, key: string): string {\n const config = this.config;\n return config.formatLabel ? config.formatLabel(value, depth, key) : String(value);\n }\n\n private renderFullWidthGroupRow(row: any, rowEl: HTMLElement, handleToggle: () => void): void {\n const config = this.config;\n const aggregators = config.aggregators ?? {};\n const groupRows = row.__groupRows ?? [];\n\n // Full-width mode: single spanning cell with toggle + label + count + aggregates\n const cell = document.createElement('div');\n cell.className = 'cell group-full';\n cell.style.gridColumn = '1 / -1';\n cell.setAttribute('role', 'gridcell');\n cell.setAttribute('data-col', '0'); // Required for focus/click delegation\n\n // Toggle button\n cell.appendChild(this.createToggleButton(row.__groupExpanded, handleToggle));\n\n // Group label\n const label = document.createElement('span');\n label.className = GridClasses.GROUP_LABEL;\n label.textContent = this.getGroupLabelText(row.__groupValue, row.__groupDepth || 0, row.__groupKey);\n cell.appendChild(label);\n\n // Row count\n if (config.showRowCount !== false) {\n const count = document.createElement('span');\n count.className = GridClasses.GROUP_COUNT;\n count.textContent = `(${row.__groupRowCount ?? row.__groupRows?.length ?? 0})`;\n cell.appendChild(count);\n }\n\n // Render aggregates if configured\n const aggregatorEntries = Object.entries(aggregators);\n if (aggregatorEntries.length > 0) {\n const aggregatesContainer = document.createElement('span');\n aggregatesContainer.className = 'group-aggregates';\n\n for (const [field, aggRef] of aggregatorEntries) {\n const col = this.columns.find((c) => c.field === field);\n const result = aggregatorRegistry.run(aggRef, groupRows, field, col);\n if (result != null) {\n const aggSpan = document.createElement('span');\n aggSpan.className = 'group-aggregate';\n aggSpan.setAttribute('data-field', field);\n // Use column header as label if available\n const colHeader = col?.header ?? field;\n aggSpan.textContent = `${colHeader}: ${result}`;\n aggregatesContainer.appendChild(aggSpan);\n }\n }\n\n if (aggregatesContainer.children.length > 0) {\n cell.appendChild(aggregatesContainer);\n }\n }\n\n rowEl.appendChild(cell);\n }\n\n private renderPerColumnGroupRow(row: any, rowEl: HTMLElement, handleToggle: () => void): void {\n const config = this.config;\n const aggregators = config.aggregators ?? {};\n const columns = this.columns;\n const groupRows = row.__groupRows ?? [];\n\n // Get grid template from the grid element\n const bodyEl = this.gridElement?.querySelector('.body') as HTMLElement | null;\n const gridTemplate = bodyEl?.style.gridTemplateColumns || '';\n if (gridTemplate) {\n rowEl.style.display = 'grid';\n rowEl.style.gridTemplateColumns = gridTemplate;\n }\n\n // Track whether we've rendered the toggle button yet (should be in first non-expander column)\n let toggleRendered = false;\n\n columns.forEach((col, colIdx) => {\n const cell = document.createElement('div');\n cell.className = 'cell group-cell';\n cell.setAttribute('data-col', String(colIdx));\n cell.setAttribute('role', 'gridcell');\n\n // Skip expander columns (they're handled by other plugins like MasterDetail/Tree)\n // but still render an empty cell to maintain grid structure\n if (isExpanderColumn(col)) {\n cell.setAttribute('data-field', col.field);\n rowEl.appendChild(cell);\n return;\n }\n\n // First non-expander column gets the toggle button + label\n if (!toggleRendered) {\n toggleRendered = true;\n cell.appendChild(this.createToggleButton(row.__groupExpanded, handleToggle));\n\n const label = document.createElement('span');\n const firstColAgg = aggregators[col.field];\n if (firstColAgg) {\n const aggResult = aggregatorRegistry.run(firstColAgg, groupRows, col.field, col);\n label.textContent = aggResult != null ? String(aggResult) : String(row.__groupValue);\n } else {\n label.textContent = this.getGroupLabelText(row.__groupValue, row.__groupDepth || 0, row.__groupKey);\n }\n cell.appendChild(label);\n\n if (config.showRowCount !== false) {\n const count = document.createElement('span');\n count.className = GridClasses.GROUP_COUNT;\n count.textContent = ` (${groupRows.length})`;\n cell.appendChild(count);\n }\n } else {\n // Other columns: run aggregator if defined\n const aggRef = aggregators[col.field];\n if (aggRef) {\n const result = aggregatorRegistry.run(aggRef, groupRows, col.field, col);\n cell.textContent = result != null ? String(result) : '';\n } else {\n cell.textContent = '';\n }\n }\n\n rowEl.appendChild(cell);\n });\n }\n // #endregion\n\n // #region Public API\n\n /**\n * Expand all groups.\n */\n expandAll(): void {\n this.expandedKeys = expandAllGroups(this.flattenedRows);\n this.emitPluginEvent('grouping-state-change', { expandedKeys: [...this.expandedKeys] });\n this.requestRender();\n }\n\n /**\n * Collapse all groups.\n */\n collapseAll(): void {\n this.expandedKeys = collapseAllGroups();\n this.emitPluginEvent('grouping-state-change', { expandedKeys: [...this.expandedKeys] });\n this.requestRender();\n }\n\n /**\n * Toggle expansion of a specific group.\n * In accordion mode, expanding a group will collapse all sibling groups.\n * @param key - The group key to toggle\n */\n toggle(key: string): void {\n const isExpanding = !this.expandedKeys.has(key);\n const config = this.config;\n\n // Find the group to get its depth for accordion mode\n const group = this.flattenedRows.find((r) => r.kind === 'group' && r.key === key) as GroupRowModelItem | undefined;\n\n // In accordion mode, collapse sibling groups when expanding\n if (config.accordion && isExpanding && group) {\n const newKeys = new Set<string>();\n // Keep only ancestors (keys that are prefixes of the current key) and the current key\n for (const existingKey of this.expandedKeys) {\n // Check if existingKey is an ancestor of the toggled key\n // Ancestors have composite keys that are prefixes of child keys (separated by '||')\n if (key.startsWith(existingKey + '||') || existingKey.startsWith(key + '||')) {\n // This is an ancestor or descendant - keep it only if ancestor\n if (key.startsWith(existingKey + '||')) {\n newKeys.add(existingKey);\n }\n } else {\n // Check depth - only keep groups at different depths\n const existingGroup = this.flattenedRows.find((r) => r.kind === 'group' && r.key === existingKey) as\n | GroupRowModelItem\n | undefined;\n if (existingGroup && existingGroup.depth !== group.depth) {\n newKeys.add(existingKey);\n }\n }\n }\n newKeys.add(key);\n this.expandedKeys = newKeys;\n } else {\n this.expandedKeys = toggleGroupExpansion(this.expandedKeys, key);\n }\n\n this.emit<GroupToggleDetail>('group-toggle', {\n key,\n expanded: this.expandedKeys.has(key),\n value: group?.value,\n depth: group?.depth ?? 0,\n });\n\n // Announce group state change for screen readers\n const expanded = this.expandedKeys.has(key);\n const groupName = group?.value != null ? String(group.value) : key;\n if (expanded) {\n const rowCount = group?.rows?.length ?? 0;\n announce(this.gridElement, getA11yMessage(this.gridElement, 'groupExpanded', groupName, rowCount));\n } else {\n announce(this.gridElement, getA11yMessage(this.gridElement, 'groupCollapsed', groupName));\n }\n\n // Notify other plugins that grouping state changed (row visibility changed)\n this.emitPluginEvent('grouping-state-change', {\n expandedKeys: [...this.expandedKeys],\n });\n\n this.requestRender();\n }\n\n /**\n * Check if a specific group is expanded.\n * @param key - The group key to check\n * @returns Whether the group is expanded\n */\n isExpanded(key: string): boolean {\n return this.expandedKeys.has(key);\n }\n\n /**\n * Expand a specific group.\n * @param key - The group key to expand\n */\n expand(key: string): void {\n if (!this.expandedKeys.has(key)) {\n this.expandedKeys = new Set([...this.expandedKeys, key]);\n this.requestRender();\n }\n }\n\n /**\n * Collapse a specific group.\n * @param key - The group key to collapse\n */\n collapse(key: string): void {\n if (this.expandedKeys.has(key)) {\n const newKeys = new Set(this.expandedKeys);\n newKeys.delete(key);\n this.expandedKeys = newKeys;\n this.requestRender();\n }\n }\n\n /**\n * Get the current group state.\n * @returns Group state information\n */\n getGroupState(): GroupState {\n const groupRows = this.flattenedRows.filter((r) => r.kind === 'group');\n return {\n isActive: this.isActive,\n expandedCount: this.expandedKeys.size,\n totalGroups: groupRows.length,\n expandedKeys: [...this.expandedKeys],\n };\n }\n\n /**\n * Get the total count of visible rows (including group headers).\n * @returns Number of visible rows\n */\n getRowCount(): number {\n return this.flattenedRows.length;\n }\n\n /**\n * Refresh the grouped row model.\n * Call this after modifying groupOn or other config options.\n */\n refreshGroups(): void {\n this.requestRender();\n }\n\n /**\n * Get current expanded group keys.\n * @returns Array of expanded group keys\n */\n getExpandedGroups(): string[] {\n return [...this.expandedKeys];\n }\n\n /**\n * Get the flattened row model.\n * @returns Array of render rows (groups + data rows)\n */\n getFlattenedRows(): RenderRow[] {\n return this.flattenedRows;\n }\n\n /**\n * Check if grouping is currently active.\n * @returns Whether grouping is active\n */\n isGroupingActive(): boolean {\n return this.isActive;\n }\n\n /**\n * Set the groupOn function dynamically.\n * @param fn - The groupOn function or undefined to disable\n */\n setGroupOn(fn: ((row: any) => any[] | any | null | false) | undefined): void {\n (this.config as GroupingRowsConfig).groupOn = fn;\n this.requestRender();\n }\n // #endregion\n}\n"],"names":["buildGroupedRowModel","rows","config","expanded","initialExpanded","groupOn","root","key","value","depth","children","Map","forEach","r","path","Array","isArray","parent","rawVal","depthIdx","seg","String","composite","node","get","set","push","size","has","length","rowIndexMap","i","effectiveExpanded","Set","flat","visit","c","isExpanded","kind","row","rowIndex","GroupingRowsPlugin","BaseGridPlugin","static","incompatibleWith","name","reason","events","type","description","queries","configRules","id","severity","message","check","accordion","defaultExpanded","styles","defaultConfig","showRowCount","indentWidth","aggregators","animation","expandedKeys","flattenedRows","isActive","previousVisibleKeys","keysToAnimate","hasAppliedDefaultExpanded","animationStyle","this","isAnimationEnabled","detach","clear","getRowHeight","_index","groupRowHeight","__isGroupRow","handleQuery","query","context","detect","enableRowGrouping","processRows","initialBuild","allKeys","filter","map","getGroupKeys","allGroupKeys","resolveDefaultExpanded","grouped","currentVisibleKeys","item","idx","add","__groupKey","__groupValue","__groupDepth","__groupRows","__groupExpanded","__groupRowCount","groupRow","__rowCacheKey","onCellClick","event","target","originalEvent","closest","GridClasses","GROUP_TOGGLE","toggle","onKeyDown","focusRow","grid","_focusRow","preventDefault","requestRenderWithFocus","renderRow","rowEl","_rowIndex","groupRowRenderer","toggleExpand","result","className","__isCustomRow","setAttribute","innerHTML","appendChild","handleToggle","style","setProperty","height","fullWidth","renderFullWidthGroupRow","renderPerColumnGroupRow","afterRender","body","gridElement","querySelector","animClass","querySelectorAll","cell","parseInt","getAttribute","classList","addEventListener","remove","once","createToggleButton","btn","document","createElement","EXPANDED","setIcon","e","stopPropagation","getGroupLabelText","formatLabel","groupRows","gridColumn","label","GROUP_LABEL","textContent","count","GROUP_COUNT","aggregatorEntries","Object","entries","aggregatesContainer","field","aggRef","col","columns","find","aggregatorRegistry","run","aggSpan","colHeader","header","bodyEl","gridTemplate","gridTemplateColumns","display","toggleRendered","colIdx","isExpanderColumn","firstColAgg","aggResult","expandAll","keys","expandAllGroups","emitPluginEvent","requestRender","collapseAll","isExpanding","group","newKeys","existingKey","startsWith","existingGroup","newSet","delete","toggleGroupExpansion","emit","groupName","rowCount","announce","getA11yMessage","expand","collapse","getGroupState","expandedCount","totalGroups","getRowCount","refreshGroups","getExpandedGroups","getFlattenedRows","isGroupingActive","setGroupOn","fn"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"grouping-rows.umd.js","sources":["../../../../../libs/grid/src/lib/plugins/grouping-rows/grouping-rows.ts","../../../../../libs/grid/src/lib/plugins/grouping-rows/GroupingRowsPlugin.ts"],"sourcesContent":["/**\n * Row Grouping Core Logic\n *\n * Pure functions for building grouped row models and aggregations.\n */\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n\nimport type { DefaultExpandedValue, GroupDefinition, GroupingRowsConfig, GroupRowModelItem, RenderRow } from './types';\n\ninterface GroupNode {\n key: string; // composite key\n value: any;\n depth: number;\n rows: any[];\n children: Map<string, GroupNode>;\n parent?: GroupNode;\n}\n\ninterface BuildGroupingArgs {\n rows: any[];\n config: GroupingRowsConfig;\n expanded: Set<string>;\n /** Initial expanded state to apply (processed by the plugin) */\n initialExpanded?: Set<string>;\n}\n\n/**\n * Build a flattened grouping projection (collapsed by default).\n * Returns empty array when groupOn not configured or all rows ungrouped.\n *\n * @param args - The grouping arguments\n * @returns Flattened array of render rows (groups + data rows)\n */\nexport function buildGroupedRowModel({ rows, config, expanded, initialExpanded }: BuildGroupingArgs): RenderRow[] {\n const groupOn = config.groupOn;\n if (typeof groupOn !== 'function') {\n return [];\n }\n\n const root: GroupNode = { key: '__root__', value: null, depth: -1, rows: [], children: new Map() };\n\n // Build tree structure — push each row into every ancestor along the path\n // so that each group's `rows` array contains ALL data rows in its subtree.\n // This is required for correct counts and aggregations on multi-level groups.\n rows.forEach((r) => {\n let path: any = groupOn(r);\n if (path == null || path === false) path = ['__ungrouped__'];\n else if (!Array.isArray(path)) path = [path];\n\n let parent = root;\n path.forEach((rawVal: any, depthIdx: number) => {\n const seg = rawVal == null ? '∅' : String(rawVal);\n const composite = parent.key === '__root__' ? seg : parent.key + '||' + seg;\n let node = parent.children.get(seg);\n if (!node) {\n node = { key: composite, value: rawVal, depth: depthIdx, rows: [], children: new Map(), parent };\n parent.children.set(seg, node);\n }\n node.rows.push(r);\n parent = node;\n });\n });\n\n // All ungrouped? treat as no grouping\n if (root.children.size === 1 && root.children.has('__ungrouped__')) {\n const only = root.children.get('__ungrouped__')!;\n if (only.rows.length === rows.length) return [];\n }\n\n // Pre-build row→index map for O(1) lookups (avoids O(n²) from rows.indexOf)\n const rowIndexMap = new Map<any, number>();\n for (let i = 0; i < rows.length; i++) {\n rowIndexMap.set(rows[i], i);\n }\n\n // Merge expanded sets - use initialExpanded on first render, then expanded takes over\n const effectiveExpanded = new Set([...expanded, ...(initialExpanded ?? [])]);\n\n // Flatten tree to array\n const flat: RenderRow[] = [];\n const visit = (node: GroupNode) => {\n if (node === root) {\n node.children.forEach((c) => visit(c));\n return;\n }\n\n const isExpanded = effectiveExpanded.has(node.key);\n flat.push({\n kind: 'group',\n key: node.key,\n value: node.value,\n depth: node.depth,\n rows: node.rows,\n expanded: isExpanded,\n });\n\n if (isExpanded) {\n if (node.children.size) {\n node.children.forEach((c) => visit(c));\n } else {\n node.rows.forEach((r) => flat.push({ kind: 'data', row: r, rowIndex: rowIndexMap.get(r) ?? -1 }));\n }\n }\n };\n visit(root);\n\n return flat;\n}\n\n/**\n * Toggle expansion state for a group key.\n *\n * @param expandedKeys - Current set of expanded keys\n * @param key - The group key to toggle\n * @returns New set with toggled state\n */\nexport function toggleGroupExpansion(expandedKeys: Set<string>, key: string): Set<string> {\n const newSet = new Set(expandedKeys);\n if (newSet.has(key)) {\n newSet.delete(key);\n } else {\n newSet.add(key);\n }\n return newSet;\n}\n\n/**\n * Expand all groups.\n *\n * @param rows - The flattened render rows\n * @returns Set of all group keys\n */\nexport function expandAllGroups(rows: RenderRow[]): Set<string> {\n const keys = new Set<string>();\n for (const row of rows) {\n if (row.kind === 'group') {\n keys.add(row.key);\n }\n }\n return keys;\n}\n\n/**\n * Collapse all groups.\n *\n * @returns Empty set\n */\nexport function collapseAllGroups(): Set<string> {\n return new Set();\n}\n\n/**\n * Resolve a defaultExpanded value to a set of keys to expand.\n * This needs to be called AFTER building the group model to get all keys.\n *\n * @param value - The defaultExpanded config value\n * @param allGroupKeys - All group keys from the model\n * @returns Set of keys to expand initially\n */\nexport function resolveDefaultExpanded(value: DefaultExpandedValue, allGroupKeys: string[]): Set<string> {\n if (value === true) {\n // Expand all groups\n return new Set(allGroupKeys);\n }\n if (value === false || value == null) {\n // Collapse all groups\n return new Set();\n }\n if (typeof value === 'number') {\n // Expand group at this index\n const key = allGroupKeys[value];\n return key ? new Set([key]) : new Set();\n }\n if (typeof value === 'string') {\n // Expand group with this key\n return new Set([value]);\n }\n if (Array.isArray(value)) {\n // Expand groups with these keys\n return new Set(value);\n }\n return new Set();\n}\n\n/**\n * Get all group keys from a flattened model.\n *\n * @param rows - The flattened render rows\n * @returns Array of group keys\n */\nexport function getGroupKeys(rows: RenderRow[]): string[] {\n return rows.filter((r): r is GroupRowModelItem => r.kind === 'group').map((r) => r.key);\n}\n\n/**\n * Count total rows in a group (including nested groups).\n *\n * @param groupRow - The group row\n * @returns Total row count\n */\nexport function getGroupRowCount(groupRow: RenderRow): number {\n if (groupRow.kind !== 'group') return 0;\n return groupRow.rows.length;\n}\n\n// #region Pre-Defined Group Model\n\ninterface PreDefinedGroupModelArgs {\n groups: GroupDefinition[];\n expanded: Set<string>;\n groupRows: Map<string, unknown[]>;\n loadingGroups: Set<string>;\n parentPath?: string[];\n}\n\n/**\n * Build a flattened render model from pre-defined group definitions.\n *\n * Unlike `buildGroupedRowModel`, this does not analyze row data — groups\n * are provided externally (e.g. from a server). Row data for each group\n * is populated lazily via the `groupRows` map.\n *\n * @param args - Pre-defined grouping arguments\n * @returns Flattened array of render rows (groups + data rows)\n */\nexport function buildPreDefinedGroupModel({\n groups,\n expanded,\n groupRows,\n loadingGroups,\n parentPath = [],\n}: PreDefinedGroupModelArgs): RenderRow[] {\n const flat: RenderRow[] = [];\n const depth = parentPath.length;\n\n for (const group of groups) {\n const currentPath = [...parentPath, group.key];\n const isExpanded = expanded.has(group.key);\n const rows = groupRows.get(group.key) ?? [];\n const isLoading = loadingGroups.has(group.key);\n\n flat.push({\n kind: 'group',\n key: group.key,\n value: group.value,\n depth,\n rows,\n expanded: isExpanded,\n });\n\n if (isExpanded) {\n // Nested child groups take priority over leaf rows\n if (group.children?.length) {\n const childRows = buildPreDefinedGroupModel({\n groups: group.children,\n expanded,\n groupRows,\n loadingGroups,\n parentPath: currentPath,\n });\n flat.push(...childRows);\n } else if (isLoading) {\n // Loading placeholder — rendered by the plugin as a loading indicator\n flat.push({ kind: 'data', row: { __loading: true, __groupKey: group.key }, rowIndex: -1 });\n } else {\n // Leaf rows from the groupRows map\n rows.forEach((row, idx) => {\n flat.push({ kind: 'data', row, rowIndex: idx });\n });\n }\n }\n }\n\n return flat;\n}\n\n/**\n * Compute the group path (array of ancestor keys) for a given group key\n * within a pre-defined group structure.\n *\n * @param groups - The group definitions to search\n * @param targetKey - The key to find\n * @param parentPath - Accumulated path (used for recursion)\n * @returns Array of group keys from root to target, or empty array if not found\n */\nexport function getGroupPath(groups: GroupDefinition[], targetKey: string, parentPath: string[] = []): string[] {\n for (const group of groups) {\n const currentPath = [...parentPath, group.key];\n if (group.key === targetKey) {\n return currentPath;\n }\n if (group.children?.length) {\n const found = getGroupPath(group.children, targetKey, currentPath);\n if (found.length > 0) return found;\n }\n }\n return [];\n}\n// #endregion\n","/**\n * Row Grouping Plugin (Class-based)\n *\n * Enables hierarchical row grouping with expand/collapse and aggregations.\n */\n\nimport { GridClasses } from '../../core/constants';\nimport { aggregatorRegistry } from '../../core/internal/aggregators';\nimport { announce, getA11yMessage } from '../../core/internal/aria';\nimport { setRowLoadingState } from '../../core/internal/loading';\nimport { BaseGridPlugin, CellClickEvent, type PluginManifest, type PluginQuery } from '../../core/plugin/base-plugin';\nimport { isExpanderColumn } from '../../core/plugin/expander-column';\nimport type { RowElementInternal } from '../../core/types';\nimport {\n buildGroupedRowModel,\n buildPreDefinedGroupModel,\n collapseAllGroups,\n expandAllGroups,\n getGroupKeys,\n getGroupPath,\n getGroupRowCount,\n resolveDefaultExpanded,\n toggleGroupExpansion,\n} from './grouping-rows';\nimport styles from './grouping-rows.css?inline';\nimport type {\n ExpandCollapseAnimation,\n GroupCollapseDetail,\n GroupDefinition,\n GroupExpandDetail,\n GroupingRowsConfig,\n GroupRowModelItem,\n GroupToggleDetail,\n RenderRow,\n} from './types';\n\n/**\n * Group state information returned by getGroupState()\n */\nexport interface GroupState {\n /** Whether grouping is currently active */\n isActive: boolean;\n /** Number of expanded groups */\n expandedCount: number;\n /** Total number of groups */\n totalGroups: number;\n /** Array of expanded group keys */\n expandedKeys: string[];\n}\n\n/**\n * Row Grouping Plugin for tbw-grid\n *\n * Organizes rows into collapsible hierarchical groups. Perfect for organizing data\n * by category, department, status, or any other dimension—or even multiple dimensions\n * for nested grouping. Includes aggregation support for summarizing group data.\n *\n * ## Installation\n *\n * ```ts\n * import { GroupingRowsPlugin } from '@toolbox-web/grid/plugins/grouping-rows';\n * ```\n *\n * ## CSS Custom Properties\n *\n * | Property | Default | Description |\n * |----------|---------|-------------|\n * | `--tbw-group-indent-width` | `1.25em` | Indentation per group level |\n * | `--tbw-grouping-rows-bg` | `var(--tbw-color-panel-bg)` | Group row background |\n * | `--tbw-grouping-rows-count-color` | `var(--tbw-color-fg-muted)` | Count badge color |\n * | `--tbw-animation-duration` | `200ms` | Expand/collapse animation |\n *\n * @example Single-Level Grouping by Department\n * ```ts\n * import { queryGrid } from '@toolbox-web/grid';\n * import { GroupingRowsPlugin } from '@toolbox-web/grid/plugins/grouping-rows';\n *\n * const grid = queryGrid('tbw-grid');\n * grid.gridConfig = {\n * columns: [\n * { field: 'name', header: 'Employee' },\n * { field: 'department', header: 'Department' },\n * { field: 'salary', header: 'Salary', type: 'currency' },\n * ],\n * plugins: [\n * new GroupingRowsPlugin({\n * groupOn: (row) => [row.department],\n * showRowCount: true,\n * defaultExpanded: false,\n * }),\n * ],\n * };\n * ```\n *\n * @example Multi-Level Grouping\n * ```ts\n * new GroupingRowsPlugin({\n * groupOn: (row) => [row.region, row.department, row.team],\n * indentWidth: 24,\n * animation: 'slide',\n * })\n * ```\n *\n * @see {@link GroupingRowsConfig} for all configuration options\n * @see {@link GroupState} for the group state structure\n *\n * @internal Extends BaseGridPlugin\n */\nexport class GroupingRowsPlugin extends BaseGridPlugin<GroupingRowsConfig> {\n /**\n * Plugin manifest - declares configuration validation rules and events.\n * @internal\n */\n static override readonly manifest: PluginManifest<GroupingRowsConfig> = {\n incompatibleWith: [\n {\n name: 'tree',\n reason:\n 'Both plugins transform the entire row model. TreePlugin flattens nested hierarchies while ' +\n 'GroupingRowsPlugin groups flat rows with synthetic headers. Use one approach per grid.',\n },\n {\n name: 'pivot',\n reason:\n 'PivotPlugin creates its own aggregated row and column structure. ' +\n 'Row grouping cannot be applied on top of pivot-generated rows.',\n },\n ],\n events: [\n {\n type: 'grouping-state-change',\n description: 'Emitted when groups are expanded/collapsed. Subscribers can react to row visibility changes.',\n },\n {\n type: 'group-expand',\n description: 'Emitted when a pre-defined group is expanded. Use to lazily load group row data.',\n },\n {\n type: 'group-collapse',\n description: 'Emitted when a pre-defined group is collapsed.',\n },\n ],\n queries: [\n {\n type: 'canMoveRow',\n description: 'Returns false for group header rows (cannot be reordered)',\n },\n ],\n configRules: [\n {\n id: 'groupingRows/accordion-defaultExpanded',\n severity: 'warn',\n message:\n `\"accordion: true\" and \"defaultExpanded\" (non-false) are used together.\\n` +\n ` → In accordion mode, only one group can be open at a time.\\n` +\n ` → Using defaultExpanded with multiple groups will collapse to one on first toggle.\\n` +\n ` → Consider using \"defaultExpanded: false\" or a single group key/index with accordion mode.`,\n check: (config) =>\n config.accordion === true &&\n config.defaultExpanded !== false &&\n config.defaultExpanded !== undefined &&\n // Allow single group expansion with accordion\n !(typeof config.defaultExpanded === 'number') &&\n !(typeof config.defaultExpanded === 'string') &&\n // Warn if true or array with multiple items\n (config.defaultExpanded === true ||\n (Array.isArray(config.defaultExpanded) && config.defaultExpanded.length > 1)),\n },\n ],\n };\n\n /** @internal */\n readonly name = 'groupingRows';\n /** @internal */\n override readonly styles = styles;\n\n /** @internal */\n protected override get defaultConfig(): Partial<GroupingRowsConfig> {\n return {\n defaultExpanded: false,\n showRowCount: true,\n indentWidth: 20,\n aggregators: {},\n animation: 'slide',\n accordion: false,\n };\n }\n\n // #region Internal State\n private expandedKeys: Set<string> = new Set();\n private flattenedRows: RenderRow[] = [];\n private isActive = false;\n private previousVisibleKeys = new Set<string>();\n private keysToAnimate = new Set<string>();\n /** Track if initial defaultExpanded has been applied */\n private hasAppliedDefaultExpanded = false;\n /** Pre-defined group definitions (server-side mode) */\n private preDefinedGroups: GroupDefinition[] = [];\n /** Lazily loaded row data keyed by group key */\n private groupRowsMap = new Map<string, unknown[]>();\n /** Groups currently in a loading state */\n private loadingGroups = new Set<string>();\n /** Whether an async groups fetch is currently in progress */\n private groupsFetchInFlight = false;\n /** Group keys with an in-flight rows fetch */\n private rowsFetchInFlight = new Set<string>();\n // #endregion\n\n // #region Animation\n\n /**\n * Get expand/collapse animation style from plugin config.\n * Uses base class isAnimationEnabled to respect grid-level settings.\n */\n private get animationStyle(): ExpandCollapseAnimation {\n if (!this.isAnimationEnabled) return false;\n return this.config.animation ?? 'slide';\n }\n\n // #endregion\n\n // #region Lifecycle\n\n /** @internal */\n override detach(): void {\n this.expandedKeys.clear();\n this.flattenedRows = [];\n this.isActive = false;\n this.previousVisibleKeys.clear();\n this.keysToAnimate.clear();\n this.hasAppliedDefaultExpanded = false;\n this.preDefinedGroups = [];\n this.groupRowsMap.clear();\n this.loadingGroups.clear();\n this.groupsFetchInFlight = false;\n this.rowsFetchInFlight.clear();\n }\n\n /**\n * Provide row height for group header rows.\n *\n * If `groupRowHeight` is configured, returns that value for group rows.\n * This allows the variable row height system to use known heights for\n * group headers without needing to measure them from the DOM.\n *\n * @param row - The row object (may be a group row)\n * @param _index - Index in the processed rows array (unused)\n * @returns Height in pixels for group rows, undefined for data rows\n *\n * @internal Plugin hook for variable row height support\n */\n override getRowHeight(row: unknown, _index: number): number | undefined {\n // Only provide height if groupRowHeight is configured\n if (this.config.groupRowHeight == null) return undefined;\n\n // Check if this is a group row\n if ((row as { __isGroupRow?: boolean }).__isGroupRow === true) {\n return this.config.groupRowHeight;\n }\n\n return undefined;\n }\n\n /**\n * Handle plugin queries.\n * @internal\n */\n override handleQuery(query: PluginQuery): unknown {\n if (query.type === 'canMoveRow') {\n // Group header rows cannot be reordered\n const row = query.context as { __isGroupRow?: boolean } | null | undefined;\n if (row?.__isGroupRow === true) {\n return false;\n }\n }\n return undefined;\n }\n // #endregion\n\n // #region Hooks\n\n /**\n * Auto-detect grouping configuration from grid config.\n * Called by plugin system to determine if plugin should activate.\n */\n static detect(rows: readonly any[], config: any): boolean {\n return (\n typeof config?.groupOn === 'function' ||\n typeof config?.enableRowGrouping === 'boolean' ||\n Array.isArray(config?.groups) ||\n typeof config?.groups === 'function'\n );\n }\n\n /** @internal */\n override processRows(rows: readonly any[]): any[] {\n // Pre-defined groups path — use external group structure instead of groupOn analysis\n if (this.preDefinedGroups.length > 0 || this.config.groups != null) {\n // If groups is an async callback and hasn't been resolved yet, trigger fetch\n if (typeof this.config.groups === 'function' && this.preDefinedGroups.length === 0 && !this.groupsFetchInFlight) {\n this.fetchGroupsAsync(this.config.groups);\n // Return empty while loading\n this.isActive = true;\n this.flattenedRows = [];\n return [];\n }\n if (this.preDefinedGroups.length > 0) {\n return this.processPreDefinedGroups();\n }\n // Static array path\n if (Array.isArray(this.config.groups) && this.config.groups.length > 0) {\n return this.processPreDefinedGroups();\n }\n // Groups callback in flight or empty result — return empty\n this.isActive = typeof this.config.groups === 'function';\n this.flattenedRows = [];\n return [];\n }\n\n const config = this.config;\n\n // Check if grouping is configured\n if (typeof config.groupOn !== 'function') {\n this.isActive = false;\n this.flattenedRows = [];\n return [...rows];\n }\n\n // First build: get structure to know all group keys\n // (needed for index-based defaultExpanded)\n const initialBuild = buildGroupedRowModel({\n rows: [...rows],\n config: config,\n expanded: new Set(), // Empty to get all root groups\n });\n\n // If no grouping produced, return original rows\n if (initialBuild.length === 0) {\n this.isActive = false;\n this.flattenedRows = [];\n return [...rows];\n }\n\n // Resolve defaultExpanded on first render only\n let initialExpanded: Set<string> | undefined;\n if (!this.hasAppliedDefaultExpanded && this.expandedKeys.size === 0 && config.defaultExpanded !== false) {\n const allKeys = getGroupKeys(initialBuild);\n initialExpanded = resolveDefaultExpanded(config.defaultExpanded ?? false, allKeys);\n\n // Mark as applied and populate expandedKeys for subsequent toggles\n if (initialExpanded.size > 0) {\n this.expandedKeys = new Set(initialExpanded);\n this.hasAppliedDefaultExpanded = true;\n }\n }\n\n // Build with proper expanded state\n const grouped = buildGroupedRowModel({\n rows: [...rows],\n config: config,\n expanded: this.expandedKeys,\n initialExpanded,\n });\n\n this.isActive = true;\n this.flattenedRows = grouped;\n\n // Track which data rows are newly visible (for animation)\n this.keysToAnimate.clear();\n const currentVisibleKeys = new Set<string>();\n grouped.forEach((item, idx) => {\n if (item.kind === 'data') {\n const key = `data-${idx}`;\n currentVisibleKeys.add(key);\n if (!this.previousVisibleKeys.has(key)) {\n this.keysToAnimate.add(key);\n }\n }\n });\n this.previousVisibleKeys = currentVisibleKeys;\n\n // Return flattened rows for rendering\n // The grid will need to handle group rows specially\n return grouped.map((item) => {\n if (item.kind === 'group') {\n return {\n __isGroupRow: true,\n __groupKey: item.key,\n __groupValue: item.value,\n __groupDepth: item.depth,\n __groupRows: item.rows,\n __groupExpanded: item.expanded,\n __groupRowCount: getGroupRowCount(item),\n // Cache key for variable row height support - survives expand/collapse\n __rowCacheKey: `group:${item.key}`,\n };\n }\n return item.row;\n });\n }\n\n /** @internal */\n override onCellClick(event: CellClickEvent): boolean | void {\n const row = event.row as Record<string, unknown> | undefined;\n\n // Check if this is a group row toggle\n if (row?.__isGroupRow) {\n const target = event.originalEvent.target as HTMLElement;\n if (target?.closest(`.${GridClasses.GROUP_TOGGLE}`)) {\n this.toggle(row.__groupKey as string);\n return true; // Prevent default\n }\n }\n }\n\n /** @internal */\n override onKeyDown(event: KeyboardEvent): boolean | void {\n // SPACE toggles expansion on group rows\n if (event.key !== ' ') return;\n\n const focusRow = this.grid._focusRow;\n const row = this.rows[focusRow] as Record<string, unknown> | undefined;\n\n // Only handle SPACE on group rows\n if (!row?.__isGroupRow) return;\n\n event.preventDefault();\n this.toggle(row.__groupKey as string);\n\n // Restore focus styling after render completes via render pipeline\n this.requestRenderWithFocus();\n return true;\n }\n\n /**\n * Render a row. Returns true if we handled the row (group row or loading placeholder), false otherwise.\n * @internal\n */\n override renderRow(row: any, rowEl: HTMLElement, _rowIndex: number): boolean {\n // Handle loading placeholder rows for pre-defined groups\n // Uses the grid's built-in row loading API for consistent, customizable spinners\n if (row?.__loading === true && row?.__groupKey) {\n rowEl.className = 'data-grid-row';\n (rowEl as RowElementInternal).__isCustomRow = true;\n rowEl.innerHTML = '';\n const cell = document.createElement('div');\n cell.className = 'cell';\n cell.style.gridColumn = '1 / -1';\n cell.setAttribute('role', 'gridcell');\n cell.textContent = '\\u00A0'; // for row height\n rowEl.appendChild(cell);\n setRowLoadingState(rowEl, true);\n return true;\n }\n\n // Only handle group rows\n if (!row?.__isGroupRow) {\n return false;\n }\n\n const config = this.config;\n\n // If a custom renderer is provided, use it\n if (config.groupRowRenderer) {\n const toggleExpand = () => {\n this.toggle(row.__groupKey);\n };\n\n const result = config.groupRowRenderer({\n key: row.__groupKey,\n value: row.__groupValue,\n depth: row.__groupDepth,\n rows: row.__groupRows,\n expanded: row.__groupExpanded,\n toggleExpand,\n });\n\n if (result) {\n rowEl.className = 'data-grid-row group-row';\n (rowEl as RowElementInternal).__isCustomRow = true; // Mark for proper class reset on recycle\n rowEl.setAttribute('data-group-depth', String(row.__groupDepth));\n if (typeof result === 'string') {\n rowEl.innerHTML = result;\n } else {\n rowEl.innerHTML = '';\n rowEl.appendChild(result);\n }\n return true;\n }\n }\n\n // Helper to toggle expansion\n const handleToggle = () => {\n this.toggle(row.__groupKey);\n };\n\n // Default group row rendering - keep data-grid-row class for focus/keyboard navigation\n rowEl.className = 'data-grid-row group-row';\n (rowEl as RowElementInternal).__isCustomRow = true; // Mark for proper class reset on recycle\n rowEl.setAttribute('data-group-depth', String(row.__groupDepth));\n rowEl.setAttribute('role', 'row');\n rowEl.setAttribute('aria-expanded', String(row.__groupExpanded));\n // Use CSS variable for depth-based indentation\n rowEl.style.setProperty('--tbw-group-depth', String(row.__groupDepth || 0));\n if (config.indentWidth !== undefined) {\n rowEl.style.setProperty('--tbw-group-indent-width', `${config.indentWidth}px`);\n }\n // Clear any inline height from previous use (e.g., responsive card mode sets height: auto)\n // This ensures group rows use CSS-defined height, not stale inline styles from recycled elements\n rowEl.style.height = '';\n rowEl.innerHTML = '';\n\n const isFullWidth = config.fullWidth !== false; // default true\n\n if (isFullWidth) {\n this.renderFullWidthGroupRow(row, rowEl, handleToggle);\n } else {\n this.renderPerColumnGroupRow(row, rowEl, handleToggle);\n }\n\n return true;\n }\n\n /** @internal */\n override afterRender(): void {\n const style = this.animationStyle;\n if (style === false || this.keysToAnimate.size === 0) return;\n\n const body = this.gridElement?.querySelector('.rows');\n if (!body) return;\n\n const animClass = style === 'fade' ? 'tbw-group-fade-in' : 'tbw-group-slide-in';\n for (const rowEl of body.querySelectorAll('.data-grid-row:not(.group-row)')) {\n const cell = rowEl.querySelector('.cell[data-row]');\n const idx = cell ? parseInt(cell.getAttribute('data-row') ?? '-1', 10) : -1;\n const item = this.flattenedRows[idx];\n const key = item?.kind === 'data' ? `data-${idx}` : undefined;\n\n if (key && this.keysToAnimate.has(key)) {\n rowEl.classList.add(animClass);\n rowEl.addEventListener('animationend', () => rowEl.classList.remove(animClass), { once: true });\n }\n }\n this.keysToAnimate.clear();\n }\n // #endregion\n\n // #region Pre-Defined Groups\n\n /**\n * Build the row model from pre-defined group definitions.\n * Used when `groups` config or `setGroups()` provides external group structure.\n */\n private processPreDefinedGroups(): any[] {\n const groups =\n this.preDefinedGroups.length > 0\n ? this.preDefinedGroups\n : Array.isArray(this.config.groups)\n ? this.config.groups\n : [];\n\n if (groups.length === 0) {\n this.isActive = false;\n this.flattenedRows = [];\n return [];\n }\n\n // Resolve defaultExpanded on first render only\n if (!this.hasAppliedDefaultExpanded && this.expandedKeys.size === 0 && this.config.defaultExpanded !== false) {\n const allKeys = this.collectGroupKeys(groups);\n const initialExpanded = resolveDefaultExpanded(this.config.defaultExpanded ?? false, allKeys);\n if (initialExpanded.size > 0) {\n this.expandedKeys = new Set(initialExpanded);\n this.hasAppliedDefaultExpanded = true;\n }\n }\n\n const grouped = buildPreDefinedGroupModel({\n groups,\n expanded: this.expandedKeys,\n groupRows: this.groupRowsMap,\n loadingGroups: this.loadingGroups,\n });\n\n this.isActive = true;\n this.flattenedRows = grouped;\n\n // Track visible data rows for animation\n this.keysToAnimate.clear();\n const currentVisibleKeys = new Set<string>();\n grouped.forEach((item, idx) => {\n if (item.kind === 'data') {\n const key = `data-${idx}`;\n currentVisibleKeys.add(key);\n if (!this.previousVisibleKeys.has(key)) {\n this.keysToAnimate.add(key);\n }\n }\n });\n this.previousVisibleKeys = currentVisibleKeys;\n\n // Return flattened rows for rendering\n return grouped.map((item) => {\n if (item.kind === 'group') {\n // Look up the pre-defined group to get rowCount from server\n const groupDef = this.findGroupDefinition(groups, item.key);\n const rowCount = groupDef?.rowCount ?? item.rows.length;\n return {\n __isGroupRow: true,\n __groupKey: item.key,\n __groupValue: item.value,\n __groupDepth: item.depth,\n __groupRows: item.rows,\n __groupExpanded: item.expanded,\n __groupRowCount: rowCount,\n __rowCacheKey: `group:${item.key}`,\n };\n }\n return item.row;\n });\n }\n\n /**\n * Fetch group definitions from an async callback.\n * Sets `preDefinedGroups` when resolved and triggers re-render.\n */\n private fetchGroupsAsync(fn: () => Promise<GroupDefinition[]>): void {\n this.groupsFetchInFlight = true;\n fn().then(\n (groups) => {\n this.groupsFetchInFlight = false;\n this.preDefinedGroups = groups;\n this.requestRender();\n },\n () => {\n // On error, clear the in-flight flag so a retry can happen\n this.groupsFetchInFlight = false;\n },\n );\n }\n\n /**\n * Fetch rows for a group using the `rows` callback and update state.\n * Manages loading indicator automatically. Guards against duplicate in-flight fetches.\n */\n private fetchGroupRowsAsync(groupKey: string, groupDef: GroupDefinition): void {\n const rowsFn = this.config.rows;\n if (!rowsFn || this.rowsFetchInFlight.has(groupKey)) return;\n\n this.rowsFetchInFlight.add(groupKey);\n this.loadingGroups.add(groupKey);\n this.requestRender();\n\n rowsFn(groupDef).then(\n (rows) => {\n this.rowsFetchInFlight.delete(groupKey);\n this.groupRowsMap.set(groupKey, rows);\n this.loadingGroups.delete(groupKey);\n this.requestRender();\n },\n () => {\n this.rowsFetchInFlight.delete(groupKey);\n this.loadingGroups.delete(groupKey);\n this.requestRender();\n },\n );\n }\n\n /**\n * Collect all group keys from a pre-defined group tree.\n */\n private collectGroupKeys(groups: GroupDefinition[]): string[] {\n const keys: string[] = [];\n for (const g of groups) {\n keys.push(g.key);\n if (g.children?.length) {\n keys.push(...this.collectGroupKeys(g.children));\n }\n }\n return keys;\n }\n\n /**\n * Get the active group definitions (pre-defined or empty).\n */\n private getActiveGroups(): GroupDefinition[] {\n if (this.preDefinedGroups.length > 0) return this.preDefinedGroups;\n return Array.isArray(this.config.groups) ? this.config.groups : [];\n }\n\n /**\n * Find a group definition by key in the pre-defined group tree.\n */\n private findGroupDefinition(groups: GroupDefinition[], key: string): GroupDefinition | undefined {\n for (const g of groups) {\n if (g.key === key) return g;\n if (g.children?.length) {\n const found = this.findGroupDefinition(g.children, key);\n if (found) return found;\n }\n }\n return undefined;\n }\n // #endregion\n\n // #region Private Rendering Helpers\n\n /**\n * Create a toggle button for expanding/collapsing a group.\n */\n private createToggleButton(expanded: boolean, handleToggle: () => void): HTMLButtonElement {\n const btn = document.createElement('button');\n btn.type = 'button';\n btn.className = `${GridClasses.GROUP_TOGGLE}${expanded ? ` ${GridClasses.EXPANDED}` : ''}`;\n btn.setAttribute('aria-label', expanded ? 'Collapse group' : 'Expand group');\n this.setIcon(btn, expanded ? 'collapse' : 'expand');\n btn.addEventListener('click', (e) => {\n e.stopPropagation();\n handleToggle();\n });\n return btn;\n }\n\n /**\n * Get the formatted label text for a group.\n */\n private getGroupLabelText(value: unknown, depth: number, key: string): string {\n const config = this.config;\n return config.formatLabel ? config.formatLabel(value, depth, key) : String(value);\n }\n\n private renderFullWidthGroupRow(row: any, rowEl: HTMLElement, handleToggle: () => void): void {\n const config = this.config;\n const aggregators = config.aggregators ?? {};\n const groupRows = row.__groupRows ?? [];\n\n // Full-width mode: single spanning cell with toggle + label + count + aggregates\n const cell = document.createElement('div');\n cell.className = 'cell group-full';\n cell.style.gridColumn = '1 / -1';\n cell.setAttribute('role', 'gridcell');\n cell.setAttribute('data-col', '0'); // Required for focus/click delegation\n\n // Toggle button\n cell.appendChild(this.createToggleButton(row.__groupExpanded, handleToggle));\n\n // Group label\n const label = document.createElement('span');\n label.className = GridClasses.GROUP_LABEL;\n label.textContent = this.getGroupLabelText(row.__groupValue, row.__groupDepth || 0, row.__groupKey);\n cell.appendChild(label);\n\n // Row count\n if (config.showRowCount !== false) {\n const count = document.createElement('span');\n count.className = GridClasses.GROUP_COUNT;\n count.textContent = `(${row.__groupRowCount ?? row.__groupRows?.length ?? 0})`;\n cell.appendChild(count);\n }\n\n // Render aggregates if configured\n const aggregatorEntries = Object.entries(aggregators);\n if (aggregatorEntries.length > 0) {\n const aggregatesContainer = document.createElement('span');\n aggregatesContainer.className = 'group-aggregates';\n\n for (const [field, aggRef] of aggregatorEntries) {\n const col = this.columns.find((c) => c.field === field);\n const result = aggregatorRegistry.run(aggRef, groupRows, field, col);\n if (result != null) {\n const aggSpan = document.createElement('span');\n aggSpan.className = 'group-aggregate';\n aggSpan.setAttribute('data-field', field);\n // Use column header as label if available\n const colHeader = col?.header ?? field;\n aggSpan.textContent = `${colHeader}: ${result}`;\n aggregatesContainer.appendChild(aggSpan);\n }\n }\n\n if (aggregatesContainer.children.length > 0) {\n cell.appendChild(aggregatesContainer);\n }\n }\n\n rowEl.appendChild(cell);\n }\n\n private renderPerColumnGroupRow(row: any, rowEl: HTMLElement, handleToggle: () => void): void {\n const config = this.config;\n const aggregators = config.aggregators ?? {};\n const columns = this.columns;\n const groupRows = row.__groupRows ?? [];\n\n // Get grid template from the grid element\n const bodyEl = this.gridElement?.querySelector('.body') as HTMLElement | null;\n const gridTemplate = bodyEl?.style.gridTemplateColumns || '';\n if (gridTemplate) {\n rowEl.style.display = 'grid';\n rowEl.style.gridTemplateColumns = gridTemplate;\n }\n\n // Track whether we've rendered the toggle button yet (should be in first non-expander column)\n let toggleRendered = false;\n\n columns.forEach((col, colIdx) => {\n const cell = document.createElement('div');\n cell.className = 'cell group-cell';\n cell.setAttribute('data-col', String(colIdx));\n cell.setAttribute('role', 'gridcell');\n\n // Skip expander columns (they're handled by other plugins like MasterDetail/Tree)\n // but still render an empty cell to maintain grid structure\n if (isExpanderColumn(col)) {\n cell.setAttribute('data-field', col.field);\n rowEl.appendChild(cell);\n return;\n }\n\n // First non-expander column gets the toggle button + label\n if (!toggleRendered) {\n toggleRendered = true;\n cell.appendChild(this.createToggleButton(row.__groupExpanded, handleToggle));\n\n const label = document.createElement('span');\n const firstColAgg = aggregators[col.field];\n if (firstColAgg) {\n const aggResult = aggregatorRegistry.run(firstColAgg, groupRows, col.field, col);\n label.textContent = aggResult != null ? String(aggResult) : String(row.__groupValue);\n } else {\n label.textContent = this.getGroupLabelText(row.__groupValue, row.__groupDepth || 0, row.__groupKey);\n }\n cell.appendChild(label);\n\n if (config.showRowCount !== false) {\n const count = document.createElement('span');\n count.className = GridClasses.GROUP_COUNT;\n count.textContent = ` (${groupRows.length})`;\n cell.appendChild(count);\n }\n } else {\n // Other columns: run aggregator if defined\n const aggRef = aggregators[col.field];\n if (aggRef) {\n const result = aggregatorRegistry.run(aggRef, groupRows, col.field, col);\n cell.textContent = result != null ? String(result) : '';\n } else {\n cell.textContent = '';\n }\n }\n\n rowEl.appendChild(cell);\n });\n }\n // #endregion\n\n // #region Public API\n\n /**\n * Expand all groups.\n */\n expandAll(): void {\n this.expandedKeys = expandAllGroups(this.flattenedRows);\n this.emitPluginEvent('grouping-state-change', { expandedKeys: [...this.expandedKeys] });\n this.requestRender();\n }\n\n /**\n * Collapse all groups.\n */\n collapseAll(): void {\n this.expandedKeys = collapseAllGroups();\n this.emitPluginEvent('grouping-state-change', { expandedKeys: [...this.expandedKeys] });\n this.requestRender();\n }\n\n /**\n * Toggle expansion of a specific group.\n * In accordion mode, expanding a group will collapse all sibling groups.\n * @param key - The group key to toggle\n */\n toggle(key: string): void {\n const isExpanding = !this.expandedKeys.has(key);\n const config = this.config;\n\n // Find the group to get its depth for accordion mode\n const group = this.flattenedRows.find((r) => r.kind === 'group' && r.key === key) as GroupRowModelItem | undefined;\n\n // In accordion mode, collapse sibling groups when expanding\n if (config.accordion && isExpanding && group) {\n const newKeys = new Set<string>();\n // Keep only ancestors (keys that are prefixes of the current key) and the current key\n for (const existingKey of this.expandedKeys) {\n // Check if existingKey is an ancestor of the toggled key\n // Ancestors have composite keys that are prefixes of child keys (separated by '||')\n if (key.startsWith(existingKey + '||') || existingKey.startsWith(key + '||')) {\n // This is an ancestor or descendant - keep it only if ancestor\n if (key.startsWith(existingKey + '||')) {\n newKeys.add(existingKey);\n }\n } else {\n // Check depth - only keep groups at different depths\n const existingGroup = this.flattenedRows.find((r) => r.kind === 'group' && r.key === existingKey) as\n | GroupRowModelItem\n | undefined;\n if (existingGroup && existingGroup.depth !== group.depth) {\n newKeys.add(existingKey);\n }\n }\n }\n newKeys.add(key);\n this.expandedKeys = newKeys;\n } else {\n this.expandedKeys = toggleGroupExpansion(this.expandedKeys, key);\n }\n\n this.emit<GroupToggleDetail>('group-toggle', {\n key,\n expanded: this.expandedKeys.has(key),\n value: group?.value,\n depth: group?.depth ?? 0,\n });\n\n // Emit group-expand/group-collapse events for pre-defined mode\n const activeGroups = this.getActiveGroups();\n if (activeGroups.length > 0) {\n const groupPath = getGroupPath(activeGroups, key);\n if (isExpanding) {\n this.emit<GroupExpandDetail>('group-expand', { groupKey: key, groupPath });\n\n // Auto-fetch rows via `rows` callback if configured and not already cached\n if (config.rows && !this.groupRowsMap.has(key)) {\n const groupDef = this.findGroupDefinition(activeGroups, key);\n if (groupDef) {\n this.fetchGroupRowsAsync(key, groupDef);\n }\n }\n } else {\n this.emit<GroupCollapseDetail>('group-collapse', { groupKey: key, groupPath });\n }\n }\n\n // Announce group state change for screen readers\n const expanded = this.expandedKeys.has(key);\n const groupName = group?.value != null ? String(group.value) : key;\n if (expanded) {\n const rowCount = group?.rows?.length ?? 0;\n announce(this.gridElement, getA11yMessage(this.gridElement, 'groupExpanded', groupName, rowCount));\n } else {\n announce(this.gridElement, getA11yMessage(this.gridElement, 'groupCollapsed', groupName));\n }\n\n // Notify other plugins that grouping state changed (row visibility changed)\n this.emitPluginEvent('grouping-state-change', {\n expandedKeys: [...this.expandedKeys],\n });\n\n this.requestRender();\n }\n\n /**\n * Check if a specific group is expanded.\n * @param key - The group key to check\n * @returns Whether the group is expanded\n */\n isExpanded(key: string): boolean {\n return this.expandedKeys.has(key);\n }\n\n /**\n * Expand a specific group.\n * @param key - The group key to expand\n */\n expand(key: string): void {\n if (!this.expandedKeys.has(key)) {\n this.expandedKeys = new Set([...this.expandedKeys, key]);\n this.requestRender();\n }\n }\n\n /**\n * Collapse a specific group.\n * @param key - The group key to collapse\n */\n collapse(key: string): void {\n if (this.expandedKeys.has(key)) {\n const newKeys = new Set(this.expandedKeys);\n newKeys.delete(key);\n this.expandedKeys = newKeys;\n this.requestRender();\n }\n }\n\n /**\n * Get the current group state.\n * @returns Group state information\n */\n getGroupState(): GroupState {\n const groupRows = this.flattenedRows.filter((r) => r.kind === 'group');\n return {\n isActive: this.isActive,\n expandedCount: this.expandedKeys.size,\n totalGroups: groupRows.length,\n expandedKeys: [...this.expandedKeys],\n };\n }\n\n /**\n * Get the total count of visible rows (including group headers).\n * @returns Number of visible rows\n */\n getRowCount(): number {\n return this.flattenedRows.length;\n }\n\n /**\n * Refresh the grouped row model.\n * Call this after modifying groupOn or other config options.\n */\n refreshGroups(): void {\n this.requestRender();\n }\n\n /**\n * Get current expanded group keys.\n * @returns Array of expanded group keys\n */\n getExpandedGroups(): string[] {\n return [...this.expandedKeys];\n }\n\n /**\n * Get the flattened row model.\n * @returns Array of render rows (groups + data rows)\n */\n getFlattenedRows(): RenderRow[] {\n return this.flattenedRows;\n }\n\n /**\n * Check if grouping is currently active.\n * @returns Whether grouping is active\n */\n isGroupingActive(): boolean {\n return this.isActive;\n }\n\n /**\n * Set the groupOn function dynamically.\n * @param fn - The groupOn function or undefined to disable\n */\n setGroupOn(fn: ((row: any) => any[] | any | null | false) | undefined): void {\n (this.config as GroupingRowsConfig).groupOn = fn;\n this.requestRender();\n }\n\n // --- Pre-defined group API ---\n\n /**\n * Replace auto-detected groups with an externally provided group structure.\n *\n * When groups are set, the plugin switches to pre-defined mode — `groupOn`\n * is ignored and the plugin renders the provided group headers instead.\n * Row data for each group must be populated via {@link setGroupRows}.\n *\n * @param groups - Array of group definitions, or empty array to clear\n */\n setGroups(groups: GroupDefinition[]): void {\n this.preDefinedGroups = groups;\n this.groupRowsMap.clear();\n this.loadingGroups.clear();\n this.rowsFetchInFlight.clear();\n this.expandedKeys.clear();\n this.hasAppliedDefaultExpanded = false;\n this.requestRender();\n }\n\n /**\n * Get the current pre-defined group structure.\n *\n * Returns the groups set via {@link setGroups} or the resolved `groups` config.\n * Returns an empty array when using `groupOn`-based grouping or while\n * an async `groups` callback is still in flight.\n *\n * @returns Current group definitions\n */\n getGroups(): GroupDefinition[] {\n if (this.preDefinedGroups.length > 0) return [...this.preDefinedGroups];\n return Array.isArray(this.config.groups) ? [...this.config.groups] : [];\n }\n\n /**\n * Populate row data for an expanded group.\n *\n * Call this in response to a `group-expand` event after fetching rows\n * from the server. The plugin will re-render to show the rows.\n *\n * If the {@link GroupingRowsConfig.rows | rows} callback is configured,\n * this is called automatically — you only need this for the imperative API.\n *\n * @param groupKey - The group key to populate\n * @param rows - The row data for this group\n */\n setGroupRows(groupKey: string, rows: unknown[]): void {\n this.groupRowsMap.set(groupKey, rows);\n this.loadingGroups.delete(groupKey);\n this.requestRender();\n }\n\n /**\n * Toggle loading indicator for a group.\n *\n * When loading is true, the group shows a loading spinner instead of row data.\n * Call with `false` after rows are loaded (also cleared by {@link setGroupRows}).\n *\n * @param groupKey - The group key\n * @param loading - Whether the group is loading\n */\n setGroupLoading(groupKey: string, loading: boolean): void {\n if (loading) {\n this.loadingGroups.add(groupKey);\n } else {\n this.loadingGroups.delete(groupKey);\n }\n this.requestRender();\n }\n\n /**\n * Clear cached row data for one or all groups.\n *\n * Use when the server data has changed and groups need to be re-fetched\n * on next expand.\n *\n * @param groupKey - Specific group key to clear, or omit to clear all\n */\n clearGroupRows(groupKey?: string): void {\n if (groupKey != null) {\n this.groupRowsMap.delete(groupKey);\n this.rowsFetchInFlight.delete(groupKey);\n } else {\n this.groupRowsMap.clear();\n this.rowsFetchInFlight.clear();\n }\n this.requestRender();\n }\n // #endregion\n}\n"],"names":["buildGroupedRowModel","rows","config","expanded","initialExpanded","groupOn","root","key","value","depth","children","Map","forEach","r","path","Array","isArray","parent","rawVal","depthIdx","seg","String","composite","node","get","set","push","size","has","length","rowIndexMap","i","effectiveExpanded","Set","flat","visit","c","isExpanded","kind","row","rowIndex","resolveDefaultExpanded","allGroupKeys","buildPreDefinedGroupModel","groups","groupRows","loadingGroups","parentPath","group","currentPath","isLoading","childRows","__loading","__groupKey","idx","getGroupPath","targetKey","found","GroupingRowsPlugin","BaseGridPlugin","static","incompatibleWith","name","reason","events","type","description","queries","configRules","id","severity","message","check","accordion","defaultExpanded","styles","defaultConfig","showRowCount","indentWidth","aggregators","animation","expandedKeys","flattenedRows","isActive","previousVisibleKeys","keysToAnimate","hasAppliedDefaultExpanded","preDefinedGroups","groupRowsMap","groupsFetchInFlight","rowsFetchInFlight","animationStyle","this","isAnimationEnabled","detach","clear","getRowHeight","_index","groupRowHeight","__isGroupRow","handleQuery","query","context","detect","enableRowGrouping","processRows","processPreDefinedGroups","fetchGroupsAsync","initialBuild","allKeys","filter","map","getGroupKeys","grouped","currentVisibleKeys","item","add","__groupValue","__groupDepth","__groupRows","__groupExpanded","__groupRowCount","groupRow","__rowCacheKey","onCellClick","event","target","originalEvent","closest","GridClasses","GROUP_TOGGLE","toggle","onKeyDown","focusRow","grid","_focusRow","preventDefault","requestRenderWithFocus","renderRow","rowEl","_rowIndex","className","__isCustomRow","innerHTML","cell","document","createElement","style","gridColumn","setAttribute","textContent","appendChild","setRowLoadingState","groupRowRenderer","toggleExpand","result","handleToggle","setProperty","height","fullWidth","renderFullWidthGroupRow","renderPerColumnGroupRow","afterRender","body","gridElement","querySelector","animClass","querySelectorAll","parseInt","getAttribute","classList","addEventListener","remove","once","collectGroupKeys","groupDef","findGroupDefinition","rowCount","fn","then","requestRender","fetchGroupRowsAsync","groupKey","rowsFn","delete","keys","g","getActiveGroups","createToggleButton","btn","EXPANDED","setIcon","e","stopPropagation","getGroupLabelText","formatLabel","label","GROUP_LABEL","count","GROUP_COUNT","aggregatorEntries","Object","entries","aggregatesContainer","field","aggRef","col","columns","find","aggregatorRegistry","run","aggSpan","colHeader","header","bodyEl","gridTemplate","gridTemplateColumns","display","toggleRendered","colIdx","isExpanderColumn","firstColAgg","aggResult","expandAll","expandAllGroups","emitPluginEvent","collapseAll","isExpanding","newKeys","existingKey","startsWith","existingGroup","newSet","toggleGroupExpansion","emit","activeGroups","groupPath","groupName","announce","getA11yMessage","expand","collapse","getGroupState","expandedCount","totalGroups","getRowCount","refreshGroups","getExpandedGroups","getFlattenedRows","isGroupingActive","setGroupOn","setGroups","getGroups","setGroupRows","setGroupLoading","loading","clearGroupRows"],"mappings":"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"}
|