@toolbox-web/grid 3.9.2 → 3.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/all.js +1 -1
  2. package/all.js.map +1 -1
  3. package/index.js +1 -1
  4. package/index.js.map +1 -1
  5. package/lib/core/types.d.ts +6 -3
  6. package/lib/features/clipboard.js.map +1 -1
  7. package/lib/features/column-virtualization.js.map +1 -1
  8. package/lib/features/context-menu.js.map +1 -1
  9. package/lib/features/editing.js.map +1 -1
  10. package/lib/features/export.js.map +1 -1
  11. package/lib/features/filtering.js.map +1 -1
  12. package/lib/features/grouping-columns.js.map +1 -1
  13. package/lib/features/grouping-rows.js.map +1 -1
  14. package/lib/features/master-detail.js.map +1 -1
  15. package/lib/features/multi-sort.js.map +1 -1
  16. package/lib/features/pinned-columns.js.map +1 -1
  17. package/lib/features/pinned-rows.js.map +1 -1
  18. package/lib/features/pivot.js.map +1 -1
  19. package/lib/features/print.js.map +1 -1
  20. package/lib/features/registry.js +1 -1
  21. package/lib/features/registry.js.map +1 -1
  22. package/lib/features/reorder-columns.js.map +1 -1
  23. package/lib/features/responsive.js.map +1 -1
  24. package/lib/features/row-drag-drop.js.map +1 -1
  25. package/lib/features/selection.js.map +1 -1
  26. package/lib/features/server-side.js.map +1 -1
  27. package/lib/features/shell.js.map +1 -1
  28. package/lib/features/sticky-rows.js.map +1 -1
  29. package/lib/features/tooltip.js.map +1 -1
  30. package/lib/features/tree.js.map +1 -1
  31. package/lib/features/undo-redo.js.map +1 -1
  32. package/lib/features/visibility.js.map +1 -1
  33. package/lib/plugins/clipboard/index.js.map +1 -1
  34. package/lib/plugins/column-virtualization/index.js.map +1 -1
  35. package/lib/plugins/context-menu/index.js.map +1 -1
  36. package/lib/plugins/editing/editors.d.ts +15 -0
  37. package/lib/plugins/editing/index.js +1 -1
  38. package/lib/plugins/editing/index.js.map +1 -1
  39. package/lib/plugins/editing/types.d.ts +5 -1
  40. package/lib/plugins/export/index.js +1 -1
  41. package/lib/plugins/export/index.js.map +1 -1
  42. package/lib/plugins/filtering/filter-model.d.ts +20 -1
  43. package/lib/plugins/filtering/filter-panel-default.d.ts +15 -1
  44. package/lib/plugins/filtering/index.d.ts +1 -1
  45. package/lib/plugins/filtering/index.js +1 -1
  46. package/lib/plugins/filtering/index.js.map +1 -1
  47. package/lib/plugins/filtering/types.d.ts +58 -3
  48. package/lib/plugins/grouping-columns/index.js.map +1 -1
  49. package/lib/plugins/grouping-rows/index.js +1 -1
  50. package/lib/plugins/grouping-rows/index.js.map +1 -1
  51. package/lib/plugins/hierarchy/index.js.map +1 -1
  52. package/lib/plugins/master-detail/index.js +1 -1
  53. package/lib/plugins/master-detail/index.js.map +1 -1
  54. package/lib/plugins/multi-sort/index.js.map +1 -1
  55. package/lib/plugins/pinned-columns/index.js.map +1 -1
  56. package/lib/plugins/pinned-rows/index.js.map +1 -1
  57. package/lib/plugins/pivot/index.js +1 -1
  58. package/lib/plugins/pivot/index.js.map +1 -1
  59. package/lib/plugins/pivot/pivot-plugin.d.ts +2 -0
  60. package/lib/plugins/pivot/types.d.ts +2 -0
  61. package/lib/plugins/print/index.js.map +1 -1
  62. package/lib/plugins/reorder-columns/index.js.map +1 -1
  63. package/lib/plugins/responsive/index.js.map +1 -1
  64. package/lib/plugins/row-drag-drop/index.js.map +1 -1
  65. package/lib/plugins/selection/index.js +1 -1
  66. package/lib/plugins/selection/index.js.map +1 -1
  67. package/lib/plugins/server-side/index.js.map +1 -1
  68. package/lib/plugins/shared/form-field-target.d.ts +10 -0
  69. package/lib/plugins/shared/format-value.d.ts +12 -0
  70. package/lib/plugins/shell/index.js.map +1 -1
  71. package/lib/plugins/sticky-rows/index.js.map +1 -1
  72. package/lib/plugins/tooltip/index.js.map +1 -1
  73. package/lib/plugins/tree/index.js +1 -1
  74. package/lib/plugins/tree/index.js.map +1 -1
  75. package/lib/plugins/undo-redo/index.js.map +1 -1
  76. package/lib/plugins/visibility/index.js.map +1 -1
  77. package/package.json +1 -1
  78. package/umd/grid.all.umd.js +1 -1
  79. package/umd/grid.all.umd.js.map +1 -1
  80. package/umd/grid.umd.js +1 -1
  81. package/umd/grid.umd.js.map +1 -1
  82. package/umd/plugins/clipboard.umd.js.map +1 -1
  83. package/umd/plugins/column-virtualization.umd.js.map +1 -1
  84. package/umd/plugins/context-menu.umd.js.map +1 -1
  85. package/umd/plugins/editing.umd.js +1 -1
  86. package/umd/plugins/editing.umd.js.map +1 -1
  87. package/umd/plugins/export.umd.js +1 -1
  88. package/umd/plugins/export.umd.js.map +1 -1
  89. package/umd/plugins/filtering.umd.js +1 -1
  90. package/umd/plugins/filtering.umd.js.map +1 -1
  91. package/umd/plugins/grouping-columns.umd.js.map +1 -1
  92. package/umd/plugins/grouping-rows.umd.js +1 -1
  93. package/umd/plugins/grouping-rows.umd.js.map +1 -1
  94. package/umd/plugins/hierarchy.umd.js.map +1 -1
  95. package/umd/plugins/master-detail.umd.js +1 -1
  96. package/umd/plugins/master-detail.umd.js.map +1 -1
  97. package/umd/plugins/multi-sort.umd.js.map +1 -1
  98. package/umd/plugins/pinned-columns.umd.js.map +1 -1
  99. package/umd/plugins/pinned-rows.umd.js.map +1 -1
  100. package/umd/plugins/pivot.umd.js +1 -1
  101. package/umd/plugins/pivot.umd.js.map +1 -1
  102. package/umd/plugins/print.umd.js.map +1 -1
  103. package/umd/plugins/reorder-columns.umd.js.map +1 -1
  104. package/umd/plugins/responsive.umd.js.map +1 -1
  105. package/umd/plugins/row-drag-drop.umd.js.map +1 -1
  106. package/umd/plugins/selection.umd.js +1 -1
  107. package/umd/plugins/selection.umd.js.map +1 -1
  108. package/umd/plugins/server-side.umd.js.map +1 -1
  109. package/umd/plugins/shell.umd.js.map +1 -1
  110. package/umd/plugins/sticky-rows.umd.js.map +1 -1
  111. package/umd/plugins/tooltip.umd.js.map +1 -1
  112. package/umd/plugins/tree.umd.js +1 -1
  113. package/umd/plugins/tree.umd.js.map +1 -1
  114. package/umd/plugins/undo-redo.umd.js.map +1 -1
  115. package/umd/plugins/visibility.umd.js.map +1 -1
@@ -1,2 +1,2 @@
1
- !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("@toolbox-web/grid")):"function"==typeof define&&define.amd?define(["exports","@toolbox-web/grid"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).TbwGridPlugin_export={},e.TbwGrid)}(this,function(e,t){function r(e,t,r=!0){let s=e;if(r&&(s=s.filter(e=>!e.hidden&&!e.field.startsWith("__")&&!0!==e.utility)),t?.length){const e=new Set(t);s=s.filter(t=>e.has(t.field))}return s}function s(e,t){return t?.length?[...t].sort((e,t)=>e-t).map(t=>e[t]).filter(e=>null!=e):e}Object.defineProperty(e,Symbol.toStringTag,{value:"Module"});var o=/^[=+\-@\t\r]/;function n(e,t){if("string"==typeof e){const r=!1!==t.escapeFormulas&&o.test(e)?`'${e}`:e,s=t.quoting??"auto";return"always"===s||"auto"===s&&(r.includes(t.delimiter)||r.includes(t.newline)||r.includes('"')||r.includes("\n")||r.includes("\r"))?`"${r.replace(/"/g,'""')}"`:r}return"number"==typeof e?String(e):"boolean"==typeof e?e?"true":"false":null==e?"":e instanceof Date?e.toISOString():"object"==typeof e?JSON.stringify(e):String(e)}function l(e,r,s,o={}){const l=o.delimiter??",",i=o.newline??"\n",a={delimiter:l,newline:i,quoting:o.quoteStrings??1?"auto":"never",escapeFormulas:o.escapeFormulas},c=[],u=o.bom?"\ufeff":"";if(!1!==s.includeHeaders){const e=r.map(e=>{const t=e.header||e.field;return n(s.processHeader?s.processHeader(t,e.field):t,a)});c.push(e.join(l))}for(const f of e){const e=r.map(e=>{let r=(0,t.resolveCellValue)(f,e);return s.processCell&&(r=s.processCell(r,e.field,f)),n(r,a)});c.push(e.join(l))}return u+c.join(i)}function i(e,t){const r=URL.createObjectURL(e),s=document.createElement("a");s.href=r,s.download=t,s.style.display="none",document.body.appendChild(s),s.click(),document.body.removeChild(s),URL.revokeObjectURL(r)}function a(e){return JSON.stringify(c(e))}function c(e){if(null==e||"object"!=typeof e)return e;if(Array.isArray(e))return e.map(c);const t={};for(const r of Object.keys(e).sort())t[r]=c(e[r]);return t}var u=class{#e=new Map;#t=0;register(e){const t=a(e),r=this.#e.get(t);if(r)return r.id;this.#t++;const s=`s${this.#t}`;return this.#e.set(t,{id:s,style:e}),s}getStyleId(e){return this.#e.get(a(e))?.id}get size(){return this.#e.size}toXml(){if(0===this.#e.size)return"";let e="\n<Styles>";for(const{id:t,style:r}of this.#e.values())e+=f(t,r);return e+="\n</Styles>",e}};function f(e,t){let r=`\n<Style ss:ID="${e}">`;if(t.font&&(r+="<Font",t.font.name&&(r+=` ss:FontName="${t.font.name}"`),t.font.size&&(r+=` ss:Size="${t.font.size}"`),t.font.bold&&(r+=' ss:Bold="1"'),t.font.italic&&(r+=' ss:Italic="1"'),t.font.color&&(r+=` ss:Color="${t.font.color}"`),r+="/>"),t.fill){const e=t.fill.pattern??"Solid";r+=`<Interior ss:Color="${t.fill.color}" ss:Pattern="${e}"/>`}return t.numberFormat&&(r+=`<NumberFormat ss:Format="${t.numberFormat}"/>`),t.alignment&&(r+="<Alignment",t.alignment.horizontal&&(r+=` ss:Horizontal="${t.alignment.horizontal}"`),t.alignment.vertical&&(r+=` ss:Vertical="${t.alignment.vertical}"`),t.alignment.wrapText&&(r+=' ss:WrapText="1"'),r+="/>"),t.borders&&(r+="<Borders>",t.borders.top&&(r+=d("Top",t.borders.top)),t.borders.bottom&&(r+=d("Bottom",t.borders.bottom)),t.borders.left&&(r+=d("Left",t.borders.left)),t.borders.right&&(r+=d("Right",t.borders.right)),r+="</Borders>"),r+="</Style>",r}function d(e,t){let r=`<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&&(r+=` ss:Color="${t.color}"`),r+="/>",r}function p(e,t,r,s,o){if(t.cellStyle){const n=t.cellStyle(r,s,o);if(n)return e.register(n)}const n=t.columnStyles?.[s];return n?e.getStyleId(n):t.defaultStyle?e.getStyleId(t.defaultStyle):void 0}function h(e,r){const s=Math.min(r.length,50);let o=(e.header??e.field).length;const n=(0,t.createFieldReader)(e.field);for(let t=0;t<s;t++){const e=n(r[t]),s=null==e?0:String(e).length;s>o&&(o=s)}return o+2}function m(e){return e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&apos;")}function g(e){return null==e?{type:"String",displayValue:""}:"number"!=typeof e||isNaN(e)?e instanceof Date?{type:"DateTime",displayValue:e.toISOString()}:{type:"String",displayValue:m(String(e))}:{type:"Number",displayValue:String(e)}}function y(e,r,s){const o=s.excelStyles,n=o?function(e){const t=new u;if(e.headerStyle&&t.register(e.headerStyle),e.groupHeaderStyle&&t.register(e.groupHeaderStyle),e.defaultStyle&&t.register(e.defaultStyle),e.columnStyles)for(const r of Object.values(e.columnStyles))t.register(r);return t}(o):void 0;let l='<?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&&o&&(l+=function(e,r,s,o){if(r.cellStyle)for(const n of s)for(const s of o){const o=r.cellStyle((0,t.resolveCellValue)(n,s),s.field,n);o&&e.register(o)}return e.toXml()}(n,o,e,r)),l+='\n<Worksheet ss:Name="Sheet1">\n<Table>',o&&(l+=function(e,t,r){const s=r.columnWidths,o=r.autoFitColumns;if(!s&&!o)return"";let n="";for(const l of e){let e=s?.[l.field];null==e&&o&&(e=h(l,t)),n+=null!=e?`\n<Column ss:Width="${7*e}"/>`:"\n<Column/>"}return n}(r,e,o));const i=o?.headerStyle&&n?n.getStyleId(o.headerStyle):void 0,a=o?.groupHeaderStyle&&n?n.getStyleId(o.groupHeaderStyle):i;return!1!==s.includeHeaders&&(l+=function(e,t){if(!e.headerRows?.length)return"";const r=t?` ss:StyleID="${t}"`:"";let s="";for(const o of e.headerRows){s+="\n<Row>";for(const e of o.cells){const t=Math.max(1,0|e.span);s+=`<Cell${r}${t>1?` ss:MergeAcross="${t-1}"`:""}><Data ss:Type="String">${m(e.label??"")}</Data></Cell>`}s+="</Row>"}return s}(s,a),l+=function(e,t,r){const s=r?` ss:StyleID="${r}"`:"";let o="\n<Row>";for(const n of e){const e=n.header||n.field;o+=`<Cell${s}><Data ss:Type="String">${m(t.processHeader?t.processHeader(e,n.field):e)}</Data></Cell>`}return o+"</Row>"}(r,s,i)),l+=function(e,r,s,o,n){let l="";for(const i of e){l+="\n<Row>";for(const e of r){let r=(0,t.resolveCellValue)(i,e);s.processCell&&(r=s.processCell(r,e.field,i));const{type:a,displayValue:c}=g(r),u=o&&n?p(o,n,r,e.field,i):void 0;l+=`<Cell${u?` ss:StyleID="${u}"`:""}><Data ss:Type="${a}">${c}</Data></Cell>`}l+="</Row>"}return l}(e,r,s,n,o),l+="\n</Table>\n</Worksheet>\n</Workbook>",l}var S=class extends t.BaseGridPlugin{static manifest={queries:[{type:"export:csv",description:"Triggers a CSV export"}]};name="export";get defaultConfig(){return{fileName:"export",includeHeaders:!0,onlyVisible:!0,onlySelected:!1}}isExportingFlag=!1;lastExportInfo=null;handleQuery(e){if("export:csv"===e.type)return this.exportCsv(),!0}resolveExportData(e,t){const o=this.config,n={format:e,fileName:t?.fileName??o.fileName??"export",includeHeaders:t?.includeHeaders??o.includeHeaders,processCell:t?.processCell,processHeader:t?.processHeader,processHeaderRow:t?.processHeaderRow,mode:t?.mode??"raw",columns:t?.columns,rowIndices:t?.rowIndices,excelStyles:t?.excelStyles,fileExtension:t?.fileExtension,headerRows:t?.headerRows},l=r(this.columns,t?.columns,o.onlyVisible);let i;if(t?.rowIndices)i=s(this.rows,t.rowIndices);else if(o.onlySelected){const e=this.getSelectionState();i=e?.selected?.size?s(this.rows,[...e.selected]):[...this.rows]}else i=[...this.rows];if(!1===n.includeHeaders)n.headerRows=void 0;else{const e=n.headerRows??this.#r(l);n.headerRows=this.#s(e,l,n.processHeaderRow)}return{columns:l,rows:i,fullParams:n}}#r(e){if(0===e.length)return[];const t=this.grid;if(!t?.query)return[];const r=t.query("collectHeaderRows",{columns:e});if(!r||0===r.length)return[];const s=[];for(const o of r)if(o)if(Array.isArray(o))for(const e of o)e&&Array.isArray(e.cells)&&e.cells.length>0&&s.push(e);else Array.isArray(o.cells)&&o.cells.length>0&&s.push(o);return s}#s(e,t,r){if(!e||0===e.length)return;const s=t.length,o=[];for(let n=0;n<e.length;n++){const t=e[n];if(!t||!Array.isArray(t.cells)||0===t.cells.length)continue;const l=[];let i=!1,a=0,c=!1;for(const e of t.cells){const t=Math.max(1,0|e.span);if(a+t>s){c=!0;break}a+=t;const o=r?r(e,n):e;null===o?l.push({label:"",span:t}):(l.push({...o,span:t}),""!==(o.label??"")&&(i=!0))}c||(a<s&&l.push({label:"",span:s-a}),i&&o.push({cells:l}))}return o.length>0?o:void 0}resolveCellOutput(e,t,r,s,o){let n=e;if("formatted"===s){const s=this.#o(t);if(s)try{const t=s(e,r);n=null==t?"":String(t)}catch{n=null==e?"":String(e)}else n="date"===t.type?function(e){if(null==e||""===e)return"";if(e instanceof Date)return isNaN(e.getTime())?"":e.toLocaleDateString();if("number"==typeof e||"string"==typeof e){const t=new Date(e);return isNaN(t.getTime())?"":t.toLocaleDateString()}return""}(e):"boolean"===t.type?!!e:e}return o&&(n=o(n,t.field,r)),n}performExport(e,t){const{columns:r,rows:s,fullParams:o}=this.resolveExportData(e,t);this.isExportingFlag=!0;let n=o.fileName??"export";const a=this.#n(r);try{switch(e){case"csv":{const e=l(this.#l(s,r,o),a,{...o,processCell:void 0},{bom:!0});n=n.endsWith(".csv")?n:`${n}.csv`,function(e,t){i(new Blob([e],{type:"text/csv;charset=utf-8;"}),t)}(e,n);break}case"excel":{const e=y(this.#l(s,r,o),a,{...o,processCell:void 0}),t=o.fileExtension??".xls",l=t.startsWith(".")?t:`.${t}`;n=n.endsWith(l)?n:`${n}${l}`,function(e,t){i(new Blob([e],{type:"application/vnd.ms-excel;charset=utf-8;"}),t)}(e,n);break}case"json":{const e=this.#l(s,r,o),t=o.headerRows&&o.headerRows.length>0?{headerRows:o.headerRows,rows:e}:e,l=JSON.stringify(t,null,2);n=n.endsWith(".json")?n:`${n}.json`;i(new Blob([l],{type:"application/json"}),n);break}}this.lastExportInfo={format:e,timestamp:new Date},this.emit("export-complete",{format:e,fileName:n,rowCount:s.length,columnCount:r.length})}finally{this.isExportingFlag=!1}}#l(e,r,s){const o=s.mode??"raw";return e.map(e=>{const n={};for(const l of r){const r=(0,t.resolveCellValue)(e,l);n[l.field]=this.resolveCellOutput(r,l,e,o,s.processCell)}return n})}getSelectionState(){try{return this.grid?.getPluginState?.("selection")??null}catch{return null}}export(e){const{columns:t,rows:r,fullParams:s}=this.resolveExportData("json",e);return this.#l(r,t,s)}getResolvedColumns(e){return r(this.columns,e?.columns,this.config.onlyVisible)}formatCsv(e,t,r){const{columns:s,fullParams:o}=this.resolveExportData("csv",t);return l(e,this.#n(s),o,r)}formatExcel(e,t){const{columns:r,fullParams:s}=this.resolveExportData("excel",t);return y(e,this.#n(r),s)}#n(e){return e.map(e=>e.valueAccessor?{...e,valueAccessor:void 0}:e)}#o(e){if(e.format)return e.format;if(!e.type)return;const t=this.grid,r=t?.__frameworkAdapter;return r?.getTypeDefault?r.getTypeDefault(e.type,t?._hostElement)?.format:void 0}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=S});
1
+ !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("@toolbox-web/grid")):"function"==typeof define&&define.amd?define(["exports","@toolbox-web/grid"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).TbwGridPlugin_export={},e.TbwGrid)}(this,function(e,t){function r(e,t,r=!0){let s=e;if(r&&(s=s.filter(e=>!e.hidden&&!e.field.startsWith("__")&&!0!==e.utility)),t?.length){const e=new Set(t);s=s.filter(t=>e.has(t.field))}return s}function s(e,t){return t?.length?[...t].sort((e,t)=>e-t).map(t=>e[t]).filter(e=>null!=e):e}Object.defineProperty(e,Symbol.toStringTag,{value:"Module"});var o=/^[=+\-@\t\r]/;function n(e,t){if("string"==typeof e){const r=!1!==t.escapeFormulas&&o.test(e)?`'${e}`:e,s=t.quoting??"auto";return"always"===s||"auto"===s&&(r.includes(t.delimiter)||r.includes(t.newline)||r.includes('"')||r.includes("\n")||r.includes("\r"))?`"${r.replace(/"/g,'""')}"`:r}return"number"==typeof e?String(e):"boolean"==typeof e?e?"true":"false":null==e?"":e instanceof Date?e.toISOString():"object"==typeof e?JSON.stringify(e):String(e)}function l(e,r,s,o={}){const l=o.delimiter??",",i=o.newline??"\n",a={delimiter:l,newline:i,quoting:o.quoteStrings??!0?"auto":"never",escapeFormulas:o.escapeFormulas},c=[],u=o.bom?"\ufeff":"";if(!1!==s.includeHeaders){const e=r.map(e=>{const t=e.header||e.field;return n(s.processHeader?s.processHeader(t,e.field):t,a)});c.push(e.join(l))}for(const f of e){const e=r.map(e=>{let r=(0,t.resolveCellValue)(f,e);return s.processCell&&(r=s.processCell(r,e.field,f)),n(r,a)});c.push(e.join(l))}return u+c.join(i)}function i(e,t){const r=URL.createObjectURL(e),s=document.createElement("a");s.href=r,s.download=t,s.style.display="none",document.body.appendChild(s),s.click(),document.body.removeChild(s),URL.revokeObjectURL(r)}function a(e){return JSON.stringify(c(e))}function c(e){if(null==e||"object"!=typeof e)return e;if(Array.isArray(e))return e.map(c);const t={};for(const r of Object.keys(e).sort())t[r]=c(e[r]);return t}var u=class{#e=new Map;#t=0;register(e){const t=a(e),r=this.#e.get(t);if(r)return r.id;this.#t++;const s=`s${this.#t}`;return this.#e.set(t,{id:s,style:e}),s}getStyleId(e){return this.#e.get(a(e))?.id}get size(){return this.#e.size}toXml(){if(0===this.#e.size)return"";let e="\n<Styles>";for(const{id:t,style:r}of this.#e.values())e+=f(t,r);return e+="\n</Styles>",e}};function f(e,t){let r=`\n<Style ss:ID="${e}">`;if(t.font&&(r+="<Font",t.font.name&&(r+=` ss:FontName="${t.font.name}"`),t.font.size&&(r+=` ss:Size="${t.font.size}"`),t.font.bold&&(r+=' ss:Bold="1"'),t.font.italic&&(r+=' ss:Italic="1"'),t.font.color&&(r+=` ss:Color="${t.font.color}"`),r+="/>"),t.fill){const e=t.fill.pattern??"Solid";r+=`<Interior ss:Color="${t.fill.color}" ss:Pattern="${e}"/>`}return t.numberFormat&&(r+=`<NumberFormat ss:Format="${t.numberFormat}"/>`),t.alignment&&(r+="<Alignment",t.alignment.horizontal&&(r+=` ss:Horizontal="${t.alignment.horizontal}"`),t.alignment.vertical&&(r+=` ss:Vertical="${t.alignment.vertical}"`),t.alignment.wrapText&&(r+=' ss:WrapText="1"'),r+="/>"),t.borders&&(r+="<Borders>",t.borders.top&&(r+=d("Top",t.borders.top)),t.borders.bottom&&(r+=d("Bottom",t.borders.bottom)),t.borders.left&&(r+=d("Left",t.borders.left)),t.borders.right&&(r+=d("Right",t.borders.right)),r+="</Borders>"),r+="</Style>",r}function d(e,t){let r=`<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&&(r+=` ss:Color="${t.color}"`),r+="/>",r}function p(e,t,r,s,o){if(t.cellStyle){const n=t.cellStyle(r,s,o);if(n)return e.register(n)}const n=t.columnStyles?.[s];return n?e.getStyleId(n):t.defaultStyle?e.getStyleId(t.defaultStyle):void 0}function h(e,r){const s=Math.min(r.length,50);let o=(e.header??e.field).length;const n=(0,t.createFieldReader)(e.field);for(let t=0;t<s;t++){const e=n(r[t]),s=null==e?0:String(e).length;s>o&&(o=s)}return o+2}function m(e){return e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&apos;")}function g(e){return null==e?{type:"String",displayValue:""}:"number"!=typeof e||isNaN(e)?e instanceof Date?{type:"DateTime",displayValue:e.toISOString()}:{type:"String",displayValue:m(String(e))}:{type:"Number",displayValue:String(e)}}function y(e,r,s){const o=s.excelStyles,n=o?function(e){const t=new u;if(e.headerStyle&&t.register(e.headerStyle),e.groupHeaderStyle&&t.register(e.groupHeaderStyle),e.defaultStyle&&t.register(e.defaultStyle),e.columnStyles)for(const r of Object.values(e.columnStyles))t.register(r);return t}(o):void 0;let l='<?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&&o&&(l+=function(e,r,s,o){if(r.cellStyle)for(const n of s)for(const s of o){const o=r.cellStyle((0,t.resolveCellValue)(n,s),s.field,n);o&&e.register(o)}return e.toXml()}(n,o,e,r)),l+='\n<Worksheet ss:Name="Sheet1">\n<Table>',o&&(l+=function(e,t,r){const s=r.columnWidths,o=r.autoFitColumns;if(!s&&!o)return"";let n="";for(const l of e){let e=s?.[l.field];null==e&&o&&(e=h(l,t)),n+=null!=e?`\n<Column ss:Width="${7*e}"/>`:"\n<Column/>"}return n}(r,e,o));const i=o?.headerStyle&&n?n.getStyleId(o.headerStyle):void 0,a=o?.groupHeaderStyle&&n?n.getStyleId(o.groupHeaderStyle):i;return!1!==s.includeHeaders&&(l+=function(e,t){if(!e.headerRows?.length)return"";const r=t?` ss:StyleID="${t}"`:"";let s="";for(const o of e.headerRows){s+="\n<Row>";for(const e of o.cells){const t=Math.max(1,0|e.span);s+=`<Cell${r}${t>1?` ss:MergeAcross="${t-1}"`:""}><Data ss:Type="String">${m(e.label??"")}</Data></Cell>`}s+="</Row>"}return s}(s,a),l+=function(e,t,r){const s=r?` ss:StyleID="${r}"`:"";let o="\n<Row>";for(const n of e){const e=n.header||n.field;o+=`<Cell${s}><Data ss:Type="String">${m(t.processHeader?t.processHeader(e,n.field):e)}</Data></Cell>`}return o+"</Row>"}(r,s,i)),l+=function(e,r,s,o,n){let l="";for(const i of e){l+="\n<Row>";for(const e of r){let r=(0,t.resolveCellValue)(i,e);s.processCell&&(r=s.processCell(r,e.field,i));const{type:a,displayValue:c}=g(r),u=o&&n?p(o,n,r,e.field,i):void 0;l+=`<Cell${u?` ss:StyleID="${u}"`:""}><Data ss:Type="${a}">${c}</Data></Cell>`}l+="</Row>"}return l}(e,r,s,n,o),l+="\n</Table>\n</Worksheet>\n</Workbook>",l}var S=class extends t.BaseGridPlugin{static manifest={queries:[{type:"export:csv",description:"Triggers a CSV export"}]};name="export";get defaultConfig(){return{fileName:"export",includeHeaders:!0,onlyVisible:!0,onlySelected:!1}}isExportingFlag=!1;lastExportInfo=null;handleQuery(e){if("export:csv"===e.type)return this.exportCsv(),!0}resolveExportData(e,t){const o=this.config,n={format:e,fileName:t?.fileName??o.fileName??"export",includeHeaders:t?.includeHeaders??o.includeHeaders,processCell:t?.processCell,processHeader:t?.processHeader,processHeaderRow:t?.processHeaderRow,mode:t?.mode??"raw",columns:t?.columns,rowIndices:t?.rowIndices,excelStyles:t?.excelStyles,fileExtension:t?.fileExtension,headerRows:t?.headerRows},l=r(this.columns,t?.columns,o.onlyVisible);let i;if(t?.rowIndices)i=s(this.rows,t.rowIndices);else if(o.onlySelected){const e=this.getSelectionState();i=e?.selected?.size?s(this.rows,[...e.selected]):[...this.rows]}else i=[...this.rows];if(!1===n.includeHeaders)n.headerRows=void 0;else{const e=n.headerRows??this.#r(l);n.headerRows=this.#s(e,l,n.processHeaderRow)}return{columns:l,rows:i,fullParams:n}}#r(e){if(0===e.length)return[];const t=this.grid;if(!t?.query)return[];const r=t.query("collectHeaderRows",{columns:e});if(!r||0===r.length)return[];const s=[];for(const o of r)if(o)if(Array.isArray(o))for(const e of o)e&&Array.isArray(e.cells)&&e.cells.length>0&&s.push(e);else Array.isArray(o.cells)&&o.cells.length>0&&s.push(o);return s}#s(e,t,r){if(!e||0===e.length)return;const s=t.length,o=[];for(let n=0;n<e.length;n++){const t=e[n];if(!t||!Array.isArray(t.cells)||0===t.cells.length)continue;const l=[];let i=!1,a=0,c=!1;for(const e of t.cells){const t=Math.max(1,0|e.span);if(a+t>s){c=!0;break}a+=t;const o=r?r(e,n):e;null===o?l.push({label:"",span:t}):(l.push({...o,span:t}),""!==(o.label??"")&&(i=!0))}c||(a<s&&l.push({label:"",span:s-a}),i&&o.push({cells:l}))}return o.length>0?o:void 0}resolveCellOutput(e,t,r,s,o){let n=e;if("formatted"===s){const s=this.#o(t);if(s)try{const t=s(e,r);n=null==t?"":String(t)}catch{n=null==e?"":String(e)}else n="date"===t.type?function(e){if(null==e||""===e)return"";if(e instanceof Date)return isNaN(e.getTime())?"":e.toLocaleDateString();if("number"==typeof e||"string"==typeof e){const t=new Date(e);return isNaN(t.getTime())?"":t.toLocaleDateString()}return""}(e):"boolean"===t.type?!!e:e}return o&&(n=o(n,t.field,r)),n}performExport(e,t){const{columns:r,rows:s,fullParams:o}=this.resolveExportData(e,t);this.isExportingFlag=!0;let n=o.fileName??"export";const a=this.#n(r);try{switch(e){case"csv":{const e=l(this.#l(s,r,o),a,{...o,processCell:void 0},{bom:!0});n=n.endsWith(".csv")?n:`${n}.csv`,function(e,t){i(new Blob([e],{type:"text/csv;charset=utf-8;"}),t)}(e,n);break}case"excel":{const e=y(this.#l(s,r,o),a,{...o,processCell:void 0}),t=o.fileExtension??".xls",l=t.startsWith(".")?t:`.${t}`;n=n.endsWith(l)?n:`${n}${l}`,function(e,t){i(new Blob([e],{type:"application/vnd.ms-excel;charset=utf-8;"}),t)}(e,n);break}case"json":{const e=this.#l(s,r,o),t=o.headerRows&&o.headerRows.length>0?{headerRows:o.headerRows,rows:e}:e,l=JSON.stringify(t,null,2);n=n.endsWith(".json")?n:`${n}.json`;i(new Blob([l],{type:"application/json"}),n);break}}this.lastExportInfo={format:e,timestamp:new Date},this.emit("export-complete",{format:e,fileName:n,rowCount:s.length,columnCount:r.length})}finally{this.isExportingFlag=!1}}#l(e,r,s){const o=s.mode??"raw";return e.map(e=>{const n={};for(const l of r){const r=(0,t.resolveCellValue)(e,l);n[l.field]=this.resolveCellOutput(r,l,e,o,s.processCell)}return n})}getSelectionState(){try{return this.grid?.getPluginState?.("selection")??null}catch{return null}}export(e){const{columns:t,rows:r,fullParams:s}=this.resolveExportData("json",e);return this.#l(r,t,s)}getResolvedColumns(e){return r(this.columns,e?.columns,this.config.onlyVisible)}formatCsv(e,t,r){const{columns:s,fullParams:o}=this.resolveExportData("csv",t);return l(e,this.#n(s),o,r)}formatExcel(e,t){const{columns:r,fullParams:s}=this.resolveExportData("excel",t);return y(e,this.#n(r),s)}#n(e){return e.map(e=>e.valueAccessor?{...e,valueAccessor:void 0}:e)}#o(e){if(e.format)return e.format;if(!e.type)return;const t=this.grid,r=t?.__frameworkAdapter;return r?.getTypeDefault?r.getTypeDefault(e.type,t?._hostElement)?.format:void 0}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=S});
2
2
  //# sourceMappingURL=export.umd.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"export.umd.js","names":[],"sources":["../../../../../libs/grid/src/lib/core/internal/utils.ts","../../../../../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/export-plugin.ts"],"sourcesContent":["// #region Environment Helpers\n\n/**\n * Check if we're running in a development environment.\n * Returns true for localhost or when NODE_ENV !== 'production'.\n * Used to show warnings only in development.\n */\nexport function isDevelopment(): boolean {\n // Check for localhost (browser environment)\n if (typeof window !== 'undefined' && window.location) {\n const hostname = window.location.hostname;\n if (hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '::1') {\n return true;\n }\n }\n // Check for NODE_ENV (build-time or SSR)\n if (typeof process !== 'undefined' && process.env?.NODE_ENV !== 'production') {\n return true;\n }\n return false;\n}\n\n// #endregion\n\n// #region Cell Rendering Helpers\n\n/**\n * Generate accessible HTML for a boolean cell.\n * Uses role=\"checkbox\" with proper aria attributes.\n */\nexport function booleanCellHTML(value: boolean): string {\n return `<span role=\"checkbox\" aria-checked=\"${value}\" aria-label=\"${value}\">${value ? '&#x1F5F9;' : '&#9744;'}</span>`;\n}\n\n/**\n * Format a date value for display.\n * Handles Date objects, timestamps, and date strings.\n * Returns empty string for invalid dates.\n */\nexport function formatDateValue(value: unknown): string {\n if (value == null || value === '') return '';\n if (value instanceof Date) {\n return isNaN(value.getTime()) ? '' : value.toLocaleDateString();\n }\n if (typeof value === 'number' || typeof value === 'string') {\n const d = new Date(value);\n return isNaN(d.getTime()) ? '' : d.toLocaleDateString();\n }\n return '';\n}\n\n/**\n * Get the row index from a cell element's data-row attribute.\n * Falls back to calculating from parent row's DOM position if data-row is missing.\n * Returns -1 if no valid row index is found.\n */\nexport function getRowIndexFromCell(cell: Element | null): number {\n if (!cell) return -1;\n const attr = cell.getAttribute('data-row');\n if (attr) return parseInt(attr, 10);\n\n // Fallback: find the parent .data-grid-row and calculate index from siblings\n const rowEl = cell.closest('.data-grid-row');\n if (!rowEl) return -1;\n\n const parent = rowEl.parentElement;\n if (!parent) return -1;\n\n // Get all data-grid-row siblings and find this row's index\n const rows = parent.querySelectorAll(':scope > .data-grid-row');\n for (let i = 0; i < rows.length; i++) {\n if (rows[i] === rowEl) return i;\n }\n return -1;\n}\n\n/**\n * Get the column index from a cell element's data-col attribute.\n * Returns -1 if no valid column index is found.\n */\nexport function getColIndexFromCell(cell: Element | null): number {\n if (!cell) return -1;\n const attr = cell.getAttribute('data-col');\n return attr ? parseInt(attr, 10) : -1;\n}\n\n/**\n * Clear all cell-focus styling from a root element.\n * Used when changing focus or when selection plugin takes over focus management.\n */\nexport function clearCellFocus(root: Element | null): void {\n if (!root) return;\n root.querySelectorAll('.cell-focus').forEach((el) => el.classList.remove('cell-focus'));\n}\n// #endregion\n\n// #region RTL Helpers\n\n/** Text direction */\nexport type TextDirection = 'ltr' | 'rtl';\n\n/**\n * Get the text direction for an element.\n * Reads from the computed style, which respects the `dir` attribute on the element\n * or any ancestor, as well as CSS `direction` property.\n *\n * @param element - The element to check direction for\n * @returns 'ltr' or 'rtl'\n *\n * @example\n * ```typescript\n * // Detect grid's direction\n * const dir = getDirection(gridElement);\n * if (dir === 'rtl') {\n * // Handle RTL layout\n * }\n * ```\n */\nexport function getDirection(element: Element): TextDirection {\n // Try computed style first (works in real browsers)\n try {\n const computedDir = getComputedStyle(element).direction;\n if (computedDir === 'rtl') return 'rtl';\n } catch {\n // getComputedStyle may fail in some test environments\n }\n\n // Fallback: check dir attribute on element or ancestors\n // This handles test environments where getComputedStyle may not reflect dir attribute\n try {\n const dirAttr = element.closest?.('[dir]')?.getAttribute('dir');\n if (dirAttr === 'rtl') return 'rtl';\n } catch {\n // closest may not be available on mock elements\n }\n\n return 'ltr';\n}\n\n/**\n * Check if an element is in RTL mode.\n *\n * @param element - The element to check\n * @returns true if the element's text direction is right-to-left\n */\nexport function isRTL(element: Element): boolean {\n return getDirection(element) === 'rtl';\n}\n\n/**\n * Resolve a logical inline position to a physical position based on text direction.\n *\n * - `'start'` → `'left'` in LTR, `'right'` in RTL\n * - `'end'` → `'right'` in LTR, `'left'` in RTL\n * - `'left'` / `'right'` → unchanged (physical values)\n *\n * @param position - Logical or physical position\n * @param direction - Text direction ('ltr' or 'rtl')\n * @returns Physical position ('left' or 'right')\n *\n * @example\n * ```typescript\n * resolveInlinePosition('start', 'ltr'); // 'left'\n * resolveInlinePosition('start', 'rtl'); // 'right'\n * resolveInlinePosition('left', 'rtl'); // 'left' (unchanged)\n * ```\n */\nexport function resolveInlinePosition(\n position: 'left' | 'right' | 'start' | 'end',\n direction: TextDirection,\n): 'left' | 'right' {\n if (position === 'left' || position === 'right') {\n return position;\n }\n if (direction === 'rtl') {\n return position === 'start' ? 'right' : 'left';\n }\n return position === 'start' ? 'left' : 'right';\n}\n// #endregion\n","/**\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.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/**\n * Leading characters that make Excel / LibreOffice / Google Sheets treat a cell\n * as a formula rather than text.\n */\nconst FORMULA_TRIGGER_RE = /^[=+\\-@\\t\\r]/;\n\n/**\n * Options for {@link formatDelimitedValue}.\n *\n * @since 3.5.0\n */\nexport interface DelimitedFormatOptions {\n /** Field separator (`,` for CSV, `\\t` for clipboard). */\n delimiter: string;\n /** Row separator. */\n newline: string;\n /**\n * `'auto'` (default) quotes only strings containing a separator or quote,\n * `'always'` quotes every string, `'never'` disables quoting entirely.\n */\n quoting?: 'auto' | 'always' | 'never';\n /**\n * Prefix formula-triggering strings with `'` so spreadsheets treat them as\n * text. Defaults to `true`.\n */\n escapeFormulas?: boolean;\n}\n\n/**\n * Format a cell value for a delimiter-separated text format (CSV export or\n * clipboard copy).\n *\n * Only string values are formula-escaped: numbers such as `-5` stringify to a\n * leading `-` but are unambiguously numeric to a spreadsheet, and escaping them\n * would turn every negative number into text.\n *\n * Dispatch order is `typeof`-first because string + number cover the vast\n * majority of cell values; the cheap typeof check skips an `instanceof Date`\n * probe (which V8 cannot fold into a fast path) for every plain string cell.\n *\n * @param value - The cell value to format\n * @param opts - Delimiter, newline, quoting and formula-escaping behaviour\n * @returns The value as a delimiter-safe string\n * @since 3.5.0\n */\nexport function formatDelimitedValue(value: unknown, opts: DelimitedFormatOptions): string {\n // Hot path: strings (most cells).\n if (typeof value === 'string') {\n const text = opts.escapeFormulas !== false && FORMULA_TRIGGER_RE.test(value) ? `'${value}` : value;\n const quoting = opts.quoting ?? 'auto';\n if (\n quoting === 'always' ||\n (quoting === 'auto' &&\n (text.includes(opts.delimiter) ||\n text.includes(opts.newline) ||\n text.includes('\"') ||\n text.includes('\\n') ||\n text.includes('\\r')))\n ) {\n return `\"${text.replace(/\"/g, '\"\"')}\"`;\n }\n return text;\n }\n if (typeof value === 'number') return String(value);\n if (typeof value === 'boolean') return value ? 'true' : 'false';\n if (value == null) return '';\n if (value instanceof Date) return value.toISOString();\n if (typeof value === 'object') return JSON.stringify(value);\n // Symbols, bigints, etc.\n return String(value);\n}\n","/**\n * CSV Export Utilities\n *\n * Functions for building and downloading CSV content.\n */\n\nimport { resolveCellValue } from '../../core/internal/value-accessor';\nimport type { ColumnConfig } from '../../core/types';\nimport { formatDelimitedValue, type DelimitedFormatOptions } from '../shared/data-collection';\nimport type { ExportParams } from './types';\n\n/**\n * CSV export options\n *\n * @since 0.1.1\n */\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 * Neutralize spreadsheet formula injection (CWE-1236) by prefixing string\n * values that start with `=`, `+`, `-`, `@`, TAB or CR with a single quote,\n * so Excel / LibreOffice / Sheets render them as text instead of evaluating\n * them (default: `true`).\n *\n * Set to `false` only when the exported data is fully trusted AND the file is\n * re-imported by a parser that must see the original characters.\n *\n * @since 3.5.0\n */\n escapeFormulas?: boolean;\n}\n\n/**\n * Format a value for CSV output.\n * Handles null, Date, objects, and strings with special characters.\n *\n * @param value - The cell value to format\n * @param quote - Quote strings containing `,`, `\"`, CR or LF (default: `true`)\n * @param escapeFormulas - Neutralize spreadsheet formula injection (default: `true`)\n */\nexport function formatCsvValue(value: any, quote = true, escapeFormulas = true): string {\n return formatDelimitedValue(value, {\n delimiter: ',',\n newline: '\\n',\n quoting: quote ? 'auto' : 'never',\n escapeFormulas,\n });\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 // Built once — buildCsv is a hot path (50K x 6 cells), so no per-cell alloc.\n const format: DelimitedFormatOptions = {\n delimiter,\n newline,\n quoting: (options.quoteStrings ?? true) ? 'auto' : 'never',\n escapeFormulas: options.escapeFormulas,\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 formatDelimitedValue(processed, format);\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 = resolveCellValue(row, col);\n if (params.processCell) {\n value = params.processCell(value, col.field, row);\n }\n return formatDelimitedValue(value, format);\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 { createFieldReader } from '../../core/internal/value-accessor';\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.groupHeaderStyle) registry.register(config.groupHeaderStyle);\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 const read = createFieldReader(col.field);\n\n for (let i = 0; i < sampleSize; i++) {\n const val = read(rows[i]);\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 { resolveCellValue } from '../../core/internal/value-accessor';\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, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&apos;');\n}\n\n/** Style registry produced by {@link buildStyleRegistry}. */\ntype StyleRegistry = ReturnType<typeof buildStyleRegistry>;\n/** Excel style options resolved from {@link ExportParams.excelStyles}. */\ntype ExcelStyles = NonNullable<ExportParams['excelStyles']>;\n\n/**\n * Emit the `<Styles>` block, pre-registering every dynamic `cellStyle` result so\n * the styles referenced by data cells actually exist in the workbook.\n */\nfunction buildStylesXml(registry: StyleRegistry, styles: ExcelStyles, rows: any[], columns: ColumnConfig[]): string {\n if (styles.cellStyle) {\n for (const row of rows) {\n for (const col of columns) {\n const dynamic = styles.cellStyle(resolveCellValue(row, col), col.field, row);\n if (dynamic) registry.register(dynamic);\n }\n }\n }\n return registry.toXml();\n}\n\n/**\n * Plugin-contributed header rows (e.g. column groups) rendered above the leaf\n * header. Each row's cells must independently span the full column count;\n * `span > 1` becomes `ss:MergeAcross=\"span-1\"` and Excel implicitly fills the\n * next `span-1` cell slots from the merge.\n */\nfunction buildPluginHeaderRowsXml(params: ExportParams, groupHeaderStyleId: string | undefined): string {\n if (!params.headerRows?.length) return '';\n\n const styleAttr = groupHeaderStyleId ? ` ss:StyleID=\"${groupHeaderStyleId}\"` : '';\n let xml = '';\n for (const headerRow of params.headerRows) {\n xml += '\\n<Row>';\n for (const cell of headerRow.cells) {\n const span = Math.max(1, cell.span | 0);\n const mergeAttr = span > 1 ? ` ss:MergeAcross=\"${span - 1}\"` : '';\n xml += `<Cell${styleAttr}${mergeAttr}><Data ss:Type=\"String\">${escapeXml(cell.label ?? '')}</Data></Cell>`;\n }\n xml += '</Row>';\n }\n return xml;\n}\n\n/** The leaf header row, one cell per exported column. */\nfunction buildHeaderRowXml(columns: ColumnConfig[], params: ExportParams, headerStyleId: string | undefined): string {\n const styleAttr = headerStyleId ? ` ss:StyleID=\"${headerStyleId}\"` : '';\n let 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${styleAttr}><Data ss:Type=\"String\">${escapeXml(processed)}</Data></Cell>`;\n }\n return xml + '</Row>';\n}\n\n/** Map a resolved cell value onto an Excel `ss:Type` + already-escaped payload. */\nfunction toExcelData(value: unknown): { type: 'Number' | 'String' | 'DateTime'; displayValue: string } {\n if (value == null) return { type: 'String', displayValue: '' };\n if (typeof value === 'number' && !isNaN(value)) return { type: 'Number', displayValue: String(value) };\n if (value instanceof Date) return { type: 'DateTime', displayValue: value.toISOString() };\n return { type: 'String', displayValue: escapeXml(String(value)) };\n}\n\n/** All data rows, applying `processCell` and per-cell style resolution. */\nfunction buildDataRowsXml(\n rows: any[],\n columns: ColumnConfig[],\n params: ExportParams,\n registry: StyleRegistry | undefined,\n styles: ExcelStyles | undefined,\n): string {\n let xml = '';\n for (const row of rows) {\n xml += '\\n<Row>';\n for (const col of columns) {\n let value = resolveCellValue(row, col);\n if (params.processCell) value = params.processCell(value, col.field, row);\n\n const { type, displayValue } = toExcelData(value);\n\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 return xml;\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 && styles) xml += buildStylesXml(registry, styles, rows, columns);\n\n xml += '\\n<Worksheet ss:Name=\"Sheet1\">\\n<Table>';\n\n // Column widths\n if (styles) xml += buildColumnWidthsXml(columns, rows as Record<string, unknown>[], styles);\n\n const headerStyleId = styles?.headerStyle && registry ? registry.getStyleId(styles.headerStyle) : undefined;\n // Group-header style ID (for plugin-contributed rows). Falls back to\n // headerStyleId so users get sensible defaults without configuring twice.\n const groupHeaderStyleId =\n styles?.groupHeaderStyle && registry ? registry.getStyleId(styles.groupHeaderStyle) : headerStyleId;\n\n if (params.includeHeaders !== false) {\n xml += buildPluginHeaderRowsXml(params, groupHeaderStyleId);\n xml += buildHeaderRowXml(columns, params, headerStyleId);\n }\n\n xml += buildDataRowsXml(rows, columns, params, registry, styles);\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\nimport { formatDateValue } from '../../core/internal/utils';\nimport { resolveCellValue } from '../../core/internal/value-accessor';\nimport { BaseGridPlugin, type PluginManifest, type PluginQuery } from '../../core/plugin/base-plugin';\nimport { type CollectHeaderRowsContext, type HeaderRowCell, type HeaderRowContribution } from '../../core/plugin/types';\nimport type { ColumnConfig, InternalGrid } from '../../core/types';\nimport { resolveColumns, resolveRows } from '../shared/data-collection';\nimport { buildCsv, type CsvOptions, downloadBlob, downloadCsv } from './csv';\nimport { buildExcelXml, downloadExcel } from './excel';\nimport type { ExportCompleteDetail, ExportConfig, ExportFormat, ExportMode, ExportParams } from './types';\n\n/**\n * Subset of {@link ExportParams} accepted by the pure formatters\n * ({@link ExportPlugin.formatCsv} / {@link ExportPlugin.formatExcel}).\n *\n * The formatters operate on already-resolved data, so options that affect\n * value resolution (`mode`, `rowIndices`, `format`, `fileName`,\n * `fileExtension`) are intentionally excluded — if you need them, run them\n * through {@link ExportPlugin.export} first or call the download/export\n * methods.\n *\n * `processCell` **is** honoured: it runs once per cell on the values you pass\n * in, just like the download methods.\n * @since 2.4.0\n */\nexport type FormatCsvParams = Pick<ExportParams, 'columns' | 'includeHeaders' | 'processCell' | 'processHeader'>;\n\n/**\n * Subset of {@link ExportParams} accepted by {@link ExportPlugin.formatExcel}.\n * Same shape as {@link FormatCsvParams} plus `excelStyles`.\n * @since 2.4.0\n */\nexport type FormatExcelParams = FormatCsvParams & Pick<ExportParams, 'excelStyles'>;\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 * @since 0.1.1\n */\nexport class ExportPlugin extends BaseGridPlugin<ExportConfig> {\n /**\n * Plugin manifest — declares queries for inter-plugin communication.\n * @internal\n */\n static override readonly manifest: PluginManifest = {\n queries: [{ type: 'export:csv', description: 'Triggers a CSV export' }],\n };\n\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 Query System\n\n /** @internal */\n override handleQuery(query: PluginQuery): unknown {\n if (query.type === 'export:csv') {\n this.exportCsv();\n return true;\n }\n return undefined;\n }\n // #endregion\n\n // #region Private Methods\n\n /**\n * Resolve the columns and rows that an export with these params would\n * include, plus a fully-defaulted ExportParams for downstream consumers.\n *\n * Honours `config.onlyVisible`, `config.onlySelected`, `params.columns`, and\n * `params.rowIndices`.\n */\n private resolveExportData(\n format: ExportFormat,\n params?: Partial<ExportParams>,\n ): { columns: ColumnConfig[]; rows: Record<string, unknown>[]; fullParams: ExportParams } {\n const config = this.config;\n\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 processHeaderRow: params?.processHeaderRow,\n mode: params?.mode ?? 'raw',\n columns: params?.columns,\n rowIndices: params?.rowIndices,\n excelStyles: params?.excelStyles,\n fileExtension: params?.fileExtension,\n headerRows: params?.headerRows,\n };\n\n const columns = resolveColumns(this.columns, params?.columns, config.onlyVisible) as ColumnConfig[];\n\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 // Resolve header rows.\n //\n // Two entry paths:\n // 1. Auto-collect: broadcast `collectHeaderRows` to all plugins.\n // 2. Manual: caller passed `params.headerRows` directly (typical for\n // pure-formatter usage where there's no grid to query).\n //\n // Both paths run through the same processing pipeline so\n // `processHeaderRow`, span validation, and blank-row dropping behave\n // identically regardless of how the rows arrived.\n //\n // All gated on `includeHeaders`: `false` skips both leaf AND contributed\n // rows (all-or-nothing per #314 design).\n if (fullParams.includeHeaders === false) {\n fullParams.headerRows = undefined;\n } else {\n const raw = fullParams.headerRows ?? this.#collectHeaderRows(columns);\n fullParams.headerRows = this.#processHeaderRows(raw, columns, fullParams.processHeaderRow);\n }\n\n return { columns, rows, fullParams };\n }\n\n /**\n * Broadcast the `'collectHeaderRows'` query to all plugins and flatten the\n * responses. Each plugin may return a single contribution or an array of\n * contributions (one per row) — both shapes are flattened to a single\n * `HeaderRowContribution[]`.\n */\n #collectHeaderRows(columns: ColumnConfig[]): HeaderRowContribution[] {\n if (columns.length === 0) return [];\n const grid = this.grid as { query?: <T>(type: string, context: CollectHeaderRowsContext) => T[] } | undefined;\n if (!grid?.query) return [];\n const responses = grid.query<HeaderRowContribution | HeaderRowContribution[] | undefined>('collectHeaderRows', {\n columns,\n });\n if (!responses || responses.length === 0) return [];\n const flat: HeaderRowContribution[] = [];\n for (const r of responses) {\n if (!r) continue;\n if (Array.isArray(r)) {\n for (const row of r) {\n if (row && Array.isArray(row.cells) && row.cells.length > 0) flat.push(row);\n }\n } else if (Array.isArray(r.cells) && r.cells.length > 0) {\n flat.push(r);\n }\n }\n return flat;\n }\n\n /**\n * Normalize, validate, and filter a list of header-row contributions.\n *\n * - Applies the optional `processHeaderRow` callback per cell. `null`\n * returns blank the cell (span preserved); rows where every cell ends\n * up blank are dropped (`#314` \"collapse fully-blank row\" rule).\n * - Validates that each row's spans sum to `columns.length`. Rows that\n * under-fill are padded with a trailing blank cell; rows that\n * over-fill (malformed third-party contribution) are skipped to keep\n * downstream formatters structurally sound.\n *\n * Returns `undefined` (not `[]`) when no row survives so downstream\n * formatters can short-circuit with a single `headerRows?.length` check.\n */\n #processHeaderRows(\n rows: HeaderRowContribution[] | undefined,\n columns: ColumnConfig[],\n processHeaderRow?: (cell: HeaderRowCell, rowIndex: number) => HeaderRowCell | null,\n ): HeaderRowContribution[] | undefined {\n if (!rows || rows.length === 0) return undefined;\n const expectedSpan = columns.length;\n\n const processed: HeaderRowContribution[] = [];\n for (let rowIndex = 0; rowIndex < rows.length; rowIndex++) {\n const row = rows[rowIndex];\n if (!row || !Array.isArray(row.cells) || row.cells.length === 0) continue;\n\n const outCells: HeaderRowCell[] = [];\n let anyVisible = false;\n let runningSpan = 0;\n let overflowed = false;\n for (const cell of row.cells) {\n const span = Math.max(1, cell.span | 0);\n if (runningSpan + span > expectedSpan) {\n // Malformed: cumulative span exceeds column count. Skip the row.\n overflowed = true;\n break;\n }\n runningSpan += span;\n const next = processHeaderRow ? processHeaderRow(cell, rowIndex) : cell;\n if (next === null) {\n outCells.push({ label: '', span });\n } else {\n outCells.push({ ...next, span });\n if ((next.label ?? '') !== '') anyVisible = true;\n }\n }\n if (overflowed) continue;\n // Pad under-filled rows with a trailing blank cell so leaf headers\n // stay aligned. (A well-behaved contribution from the grouping plugin\n // always fully covers `columns.length`; this guards against\n // third-party plugins that drift.)\n if (runningSpan < expectedSpan) {\n outCells.push({ label: '', span: expectedSpan - runningSpan });\n }\n if (anyVisible) processed.push({ cells: outCells });\n // else: every cell collapsed → row dropped.\n }\n\n return processed.length > 0 ? processed : undefined;\n }\n\n /**\n * Apply the configured export `mode` to a single cell value.\n *\n * - `'raw'` : returns the underlying value (after optional `processCell`).\n * - `'formatted'` : applies the column-type default formatter and `column.format`,\n * returning the same string the grid displays. `processCell`\n * runs last on the formatted value.\n */\n private resolveCellOutput(\n value: unknown,\n col: ColumnConfig,\n row: Record<string, unknown>,\n mode: ExportMode,\n processCell?: ExportParams['processCell'],\n ): unknown {\n let out: unknown = value;\n\n if (mode === 'formatted') {\n const formatFn = this.#resolveFormatFn(col);\n if (formatFn) {\n try {\n const formatted = formatFn(value, row);\n out = formatted == null ? '' : String(formatted);\n } catch {\n out = value == null ? '' : String(value);\n }\n } else if (col.type === 'date') {\n out = formatDateValue(value);\n } else if (col.type === 'boolean') {\n out = !!value;\n } else {\n out = value;\n }\n }\n\n if (processCell) {\n out = processCell(out, col.field, row);\n }\n\n return out;\n }\n\n private performExport(format: ExportFormat, params?: Partial<ExportParams>): void {\n const { columns, rows, fullParams } = this.resolveExportData(format, params);\n\n this.isExportingFlag = true;\n let fileName = fullParams.fileName ?? 'export';\n\n // buildCsv / buildExcelXml read each cell via `resolveCellValue(row, col)`.\n // Our pre-resolved row objects are keyed by `column.field`, so any\n // `valueAccessor` on the column would misfire on these synthetic rows.\n // Strip accessors before delegating to keep the lookup as a plain field read.\n const downstreamColumns = this.#stripAccessors(columns);\n\n try {\n switch (format) {\n case 'csv': {\n const data = this.#buildExportData(rows, columns, fullParams);\n // processCell already applied during data resolution — strip it so buildCsv\n // doesn't double-apply.\n const content = buildCsv(data, downstreamColumns, { ...fullParams, processCell: undefined }, { bom: true });\n fileName = fileName.endsWith('.csv') ? fileName : `${fileName}.csv`;\n downloadCsv(content, fileName);\n break;\n }\n\n case 'excel': {\n const data = this.#buildExportData(rows, columns, fullParams);\n const content = buildExcelXml(data, downstreamColumns, { ...fullParams, processCell: undefined });\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 data = this.#buildExportData(rows, columns, fullParams);\n // When plugins contributed header rows (e.g. column groups), emit an\n // envelope `{ headerRows, rows }` so consumers can reconstruct\n // multi-level headers. Without contributions the output stays a flat\n // array — non-breaking for existing JSON consumers.\n const payload =\n fullParams.headerRows && fullParams.headerRows.length > 0\n ? { headerRows: fullParams.headerRows, rows: data }\n : data;\n const content = JSON.stringify(payload, 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 /**\n * Resolve every cell into a plain object keyed by `column.field`, applying\n * the configured `mode` and `processCell` once. The result can be fed to\n * `buildCsv` / `buildExcelXml` directly — they read values via\n * `resolveCellValue`, which on plain key/value objects is a simple\n * `row[field]` lookup, so no double-resolution occurs.\n */\n #buildExportData(\n rows: Record<string, unknown>[],\n columns: ColumnConfig[],\n params: ExportParams,\n ): Record<string, unknown>[] {\n const mode = params.mode ?? 'raw';\n return rows.map((row) => {\n const obj: Record<string, unknown> = {};\n for (const col of columns) {\n const raw = resolveCellValue(row, col);\n obj[col.field] = this.resolveCellOutput(raw, col, row, mode, params.processCell);\n }\n return obj;\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 * Returns the row data that would be included in an export, without\n * producing a file. Honours `mode`, `onlyVisible`, `onlySelected`,\n * `columns`, `rowIndices`, `processCell`, and (for `mode: 'formatted'`)\n * `column.format`.\n *\n * Each returned object is keyed by `column.field` in column order.\n *\n * @example\n * ```ts\n * // Underlying typed values (Date stays Date, number stays number)\n * const raw = exporter.export();\n *\n * // What the user sees in each cell\n * const display = exporter.export({ mode: 'formatted' });\n * ```\n */\n export(params?: Partial<ExportParams>): Record<string, unknown>[] {\n const { columns, rows, fullParams } = this.resolveExportData('json', params);\n return this.#buildExportData(rows, columns, fullParams);\n }\n\n /**\n * Returns the columns (in order) that an export with these params would\n * include. Useful when handing rows to a third-party serializer that needs\n * header labels, widths, or types alongside the data.\n */\n getResolvedColumns(params?: Partial<ExportParams>): ColumnConfig[] {\n return resolveColumns(this.columns, params?.columns, this.config.onlyVisible) as ColumnConfig[];\n }\n\n /**\n * Format an array of row objects as a CSV string. Column order, headers,\n * and which fields to emit are taken from the plugin's resolved columns\n * (respecting `onlyVisible` and `params.columns`).\n *\n * This is a **pure formatter** — it does not re-resolve values from the\n * grid. Pass `data` produced by {@link ExportPlugin.export} (or any\n * compatible row objects keyed by `column.field`).\n *\n * `params.processCell` is honoured: it runs once per cell on the values in\n * `data`. `mode` is **not** accepted here — apply it upstream via\n * `export({ mode: 'formatted' })`.\n *\n * @example\n * ```ts\n * const csv = exporter.formatCsv(exporter.export());\n * await navigator.clipboard.writeText(csv);\n * ```\n */\n formatCsv(data: Record<string, unknown>[], params?: FormatCsvParams, options?: CsvOptions): string {\n const { columns, fullParams } = this.resolveExportData('csv', params);\n return buildCsv(data, this.#stripAccessors(columns), fullParams, options);\n }\n\n /**\n * Format an array of row objects as an Excel XML Spreadsheet 2003 string.\n * See {@link formatCsv} for the data-shape contract — this method is also a\n * pure formatter and `params.processCell` is honoured the same way.\n */\n formatExcel(data: Record<string, unknown>[], params?: FormatExcelParams): string {\n const { columns, fullParams } = this.resolveExportData('excel', params);\n return buildExcelXml(data, this.#stripAccessors(columns), fullParams);\n }\n\n /** @internal Drop `valueAccessor` so downstream builders read pre-resolved fields. */\n #stripAccessors(columns: ColumnConfig[]): ColumnConfig[] {\n return columns.map((c) => (c.valueAccessor ? ({ ...c, valueAccessor: undefined } as ColumnConfig) : c));\n }\n\n /**\n * @internal Resolve the format function for a column for `mode: 'formatted'`.\n * Mirrors the priority chain in core/internal/rows.ts (`column.format` → adapter\n * type default), but inlined so this module stays free of browser-only imports.\n */\n #resolveFormatFn(col: ColumnConfig): ((value: unknown, row: unknown) => string) | undefined {\n if (col.format) return col.format as (value: unknown, row: unknown) => string;\n if (!col.type) return undefined;\n // The grid host carries optional `__frameworkAdapter` and `_hostElement` from\n // InternalGrid. Narrow with a structural guard rather than an `as unknown as`\n // cast — these keys are documented public-internal in core/types.ts.\n const grid = this.grid as Partial<Pick<InternalGrid<unknown>, '__frameworkAdapter' | '_hostElement'>> | undefined;\n const adapter = grid?.__frameworkAdapter;\n if (!adapter?.getTypeDefault) return undefined;\n const appDefault = adapter.getTypeDefault(col.type, grid?._hostElement);\n return appDefault?.format as ((value: unknown, row: unknown) => string) | undefined;\n }\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"],"mappings":"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"}
1
+ {"version":3,"file":"export.umd.js","names":["resolveCellValue","createFieldReader","resolveCellValue","BaseGridPlugin","resolveCellValue","global","factory","exports","module","require","define","amd","globalThis","self","TbwGridPlugin_export","TbwGrid","this","_toolbox_web_grid","resolveColumns","columns","fields","onlyVisible","result","filter","c","hidden","field","startsWith","utility","length","fieldSet","Set","has","resolveRows","rows","indices","sort","a","b","map","i","r","Object","defineProperty","Symbol","toStringTag","value","FORMULA_TRIGGER_RE","formatDelimitedValue","opts","text","escapeFormulas","test","quoting","includes","delimiter","newline","replace","String","Date","toISOString","JSON","stringify","buildCsv","params","options","format","quoteStrings","lines","bom","includeHeaders","headerRow","col","header","processHeader","push","join","row","cells","resolveCellValue","processCell","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","keys","StyleRegistry","entries","Map","counter","register","hash","existing","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","cellStyle","dynamic","colStyle","columnStyles","defaultStyle","autoFitWidth","sampleSize","Math","min","maxLen","read","createFieldReader","val","len","escapeXml","str","toExcelData","type","displayValue","isNaN","buildExcelXml","styles","excelStyles","headerStyle","groupHeaderStyle","buildStyleRegistry","buildStylesXml","widths","columnWidths","autoFit","autoFitColumns","width","buildColumnWidthsXml","headerStyleId","groupHeaderStyleId","headerRows","styleAttr","cell","span","max","label","buildPluginHeaderRowsXml","buildHeaderRowXml","dataStyleId","buildDataRowsXml","ExportPlugin","BaseGridPlugin","static","queries","description","defaultConfig","onlySelected","isExportingFlag","lastExportInfo","handleQuery","query","exportCsv","resolveExportData","fullParams","processHeaderRow","mode","rowIndices","fileExtension","selectionState","getSelectionState","selected","raw","collectHeaderRows","processHeaderRows","grid","responses","flat","expectedSpan","processed","rowIndex","outCells","anyVisible","runningSpan","overflowed","next","resolveCellOutput","out","formatFn","resolveFormatFn","formatted","getTime","toLocaleDateString","d","formatDateValue","performExport","downstreamColumns","stripAccessors","content","buildExportData","endsWith","Blob","downloadCsv","ext","normalizedExt","downloadExcel","data","payload","timestamp","emit","rowCount","columnCount","getPluginState","getResolvedColumns","formatCsv","formatExcel","valueAccessor","adapter","__frameworkAdapter","getTypeDefault","_hostElement","exportExcel","exportJson","isExporting","getLastExport"],"sources":["../../../../../libs/grid/src/lib/core/internal/utils.ts","../../../../../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/export-plugin.ts"],"sourcesContent":["// #region Environment Helpers\n\n/**\n * Check if we're running in a development environment.\n * Returns true for localhost or when NODE_ENV !== 'production'.\n * Used to show warnings only in development.\n */\nexport function isDevelopment(): boolean {\n // Check for localhost (browser environment)\n if (typeof window !== 'undefined' && window.location) {\n const hostname = window.location.hostname;\n if (hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '::1') {\n return true;\n }\n }\n // Check for NODE_ENV (build-time or SSR)\n if (typeof process !== 'undefined' && process.env?.NODE_ENV !== 'production') {\n return true;\n }\n return false;\n}\n\n// #endregion\n\n// #region Cell Rendering Helpers\n\n/**\n * Generate accessible HTML for a boolean cell.\n * Uses role=\"checkbox\" with proper aria attributes.\n */\nexport function booleanCellHTML(value: boolean): string {\n return `<span role=\"checkbox\" aria-checked=\"${value}\" aria-label=\"${value}\">${value ? '&#x1F5F9;' : '&#9744;'}</span>`;\n}\n\n/**\n * Format a date value for display.\n * Handles Date objects, timestamps, and date strings.\n * Returns empty string for invalid dates.\n */\nexport function formatDateValue(value: unknown): string {\n if (value == null || value === '') return '';\n if (value instanceof Date) {\n return isNaN(value.getTime()) ? '' : value.toLocaleDateString();\n }\n if (typeof value === 'number' || typeof value === 'string') {\n const d = new Date(value);\n return isNaN(d.getTime()) ? '' : d.toLocaleDateString();\n }\n return '';\n}\n\n/**\n * Get the row index from a cell element's data-row attribute.\n * Falls back to calculating from parent row's DOM position if data-row is missing.\n * Returns -1 if no valid row index is found.\n */\nexport function getRowIndexFromCell(cell: Element | null): number {\n if (!cell) return -1;\n const attr = cell.getAttribute('data-row');\n if (attr) return parseInt(attr, 10);\n\n // Fallback: find the parent .data-grid-row and calculate index from siblings\n const rowEl = cell.closest('.data-grid-row');\n if (!rowEl) return -1;\n\n const parent = rowEl.parentElement;\n if (!parent) return -1;\n\n // Get all data-grid-row siblings and find this row's index\n const rows = parent.querySelectorAll(':scope > .data-grid-row');\n for (let i = 0; i < rows.length; i++) {\n if (rows[i] === rowEl) return i;\n }\n return -1;\n}\n\n/**\n * Get the column index from a cell element's data-col attribute.\n * Returns -1 if no valid column index is found.\n */\nexport function getColIndexFromCell(cell: Element | null): number {\n if (!cell) return -1;\n const attr = cell.getAttribute('data-col');\n return attr ? parseInt(attr, 10) : -1;\n}\n\n/**\n * Clear all cell-focus styling from a root element.\n * Used when changing focus or when selection plugin takes over focus management.\n */\nexport function clearCellFocus(root: Element | null): void {\n if (!root) return;\n root.querySelectorAll('.cell-focus').forEach((el) => el.classList.remove('cell-focus'));\n}\n// #endregion\n\n// #region RTL Helpers\n\n/** Text direction */\nexport type TextDirection = 'ltr' | 'rtl';\n\n/**\n * Get the text direction for an element.\n * Reads from the computed style, which respects the `dir` attribute on the element\n * or any ancestor, as well as CSS `direction` property.\n *\n * @param element - The element to check direction for\n * @returns 'ltr' or 'rtl'\n *\n * @example\n * ```typescript\n * // Detect grid's direction\n * const dir = getDirection(gridElement);\n * if (dir === 'rtl') {\n * // Handle RTL layout\n * }\n * ```\n */\nexport function getDirection(element: Element): TextDirection {\n // Try computed style first (works in real browsers)\n try {\n const computedDir = getComputedStyle(element).direction;\n if (computedDir === 'rtl') return 'rtl';\n } catch {\n // getComputedStyle may fail in some test environments\n }\n\n // Fallback: check dir attribute on element or ancestors\n // This handles test environments where getComputedStyle may not reflect dir attribute\n try {\n const dirAttr = element.closest?.('[dir]')?.getAttribute('dir');\n if (dirAttr === 'rtl') return 'rtl';\n } catch {\n // closest may not be available on mock elements\n }\n\n return 'ltr';\n}\n\n/**\n * Check if an element is in RTL mode.\n *\n * @param element - The element to check\n * @returns true if the element's text direction is right-to-left\n */\nexport function isRTL(element: Element): boolean {\n return getDirection(element) === 'rtl';\n}\n\n/**\n * Resolve a logical inline position to a physical position based on text direction.\n *\n * - `'start'` → `'left'` in LTR, `'right'` in RTL\n * - `'end'` → `'right'` in LTR, `'left'` in RTL\n * - `'left'` / `'right'` → unchanged (physical values)\n *\n * @param position - Logical or physical position\n * @param direction - Text direction ('ltr' or 'rtl')\n * @returns Physical position ('left' or 'right')\n *\n * @example\n * ```typescript\n * resolveInlinePosition('start', 'ltr'); // 'left'\n * resolveInlinePosition('start', 'rtl'); // 'right'\n * resolveInlinePosition('left', 'rtl'); // 'left' (unchanged)\n * ```\n */\nexport function resolveInlinePosition(\n position: 'left' | 'right' | 'start' | 'end',\n direction: TextDirection,\n): 'left' | 'right' {\n if (position === 'left' || position === 'right') {\n return position;\n }\n if (direction === 'rtl') {\n return position === 'start' ? 'right' : 'left';\n }\n return position === 'start' ? 'left' : 'right';\n}\n// #endregion\n","/**\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.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/**\n * Leading characters that make Excel / LibreOffice / Google Sheets treat a cell\n * as a formula rather than text.\n */\nconst FORMULA_TRIGGER_RE = /^[=+\\-@\\t\\r]/;\n\n/**\n * Options for {@link formatDelimitedValue}.\n *\n * @since 3.5.0\n */\nexport interface DelimitedFormatOptions {\n /** Field separator (`,` for CSV, `\\t` for clipboard). */\n delimiter: string;\n /** Row separator. */\n newline: string;\n /**\n * `'auto'` (default) quotes only strings containing a separator or quote,\n * `'always'` quotes every string, `'never'` disables quoting entirely.\n */\n quoting?: 'auto' | 'always' | 'never';\n /**\n * Prefix formula-triggering strings with `'` so spreadsheets treat them as\n * text. Defaults to `true`.\n */\n escapeFormulas?: boolean;\n}\n\n/**\n * Format a cell value for a delimiter-separated text format (CSV export or\n * clipboard copy).\n *\n * Only string values are formula-escaped: numbers such as `-5` stringify to a\n * leading `-` but are unambiguously numeric to a spreadsheet, and escaping them\n * would turn every negative number into text.\n *\n * Dispatch order is `typeof`-first because string + number cover the vast\n * majority of cell values; the cheap typeof check skips an `instanceof Date`\n * probe (which V8 cannot fold into a fast path) for every plain string cell.\n *\n * @param value - The cell value to format\n * @param opts - Delimiter, newline, quoting and formula-escaping behaviour\n * @returns The value as a delimiter-safe string\n * @since 3.5.0\n */\nexport function formatDelimitedValue(value: unknown, opts: DelimitedFormatOptions): string {\n // Hot path: strings (most cells).\n if (typeof value === 'string') {\n const text = opts.escapeFormulas !== false && FORMULA_TRIGGER_RE.test(value) ? `'${value}` : value;\n const quoting = opts.quoting ?? 'auto';\n if (\n quoting === 'always' ||\n (quoting === 'auto' &&\n (text.includes(opts.delimiter) ||\n text.includes(opts.newline) ||\n text.includes('\"') ||\n text.includes('\\n') ||\n text.includes('\\r')))\n ) {\n return `\"${text.replace(/\"/g, '\"\"')}\"`;\n }\n return text;\n }\n if (typeof value === 'number') return String(value);\n if (typeof value === 'boolean') return value ? 'true' : 'false';\n if (value == null) return '';\n if (value instanceof Date) return value.toISOString();\n if (typeof value === 'object') return JSON.stringify(value);\n // Symbols, bigints, etc.\n return String(value);\n}\n","/**\n * CSV Export Utilities\n *\n * Functions for building and downloading CSV content.\n */\n\nimport { resolveCellValue } from '../../core/internal/value-accessor';\nimport type { ColumnConfig } from '../../core/types';\nimport { formatDelimitedValue, type DelimitedFormatOptions } from '../shared/data-collection';\nimport type { ExportParams } from './types';\n\n/**\n * CSV export options\n *\n * @since 0.1.1\n */\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 * Neutralize spreadsheet formula injection (CWE-1236) by prefixing string\n * values that start with `=`, `+`, `-`, `@`, TAB or CR with a single quote,\n * so Excel / LibreOffice / Sheets render them as text instead of evaluating\n * them (default: `true`).\n *\n * Set to `false` only when the exported data is fully trusted AND the file is\n * re-imported by a parser that must see the original characters.\n *\n * @since 3.5.0\n */\n escapeFormulas?: boolean;\n}\n\n/**\n * Format a value for CSV output.\n * Handles null, Date, objects, and strings with special characters.\n *\n * @param value - The cell value to format\n * @param quote - Quote strings containing `,`, `\"`, CR or LF (default: `true`)\n * @param escapeFormulas - Neutralize spreadsheet formula injection (default: `true`)\n */\nexport function formatCsvValue(value: any, quote = true, escapeFormulas = true): string {\n return formatDelimitedValue(value, {\n delimiter: ',',\n newline: '\\n',\n quoting: quote ? 'auto' : 'never',\n escapeFormulas,\n });\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 // Built once — buildCsv is a hot path (50K x 6 cells), so no per-cell alloc.\n const format: DelimitedFormatOptions = {\n delimiter,\n newline,\n quoting: (options.quoteStrings ?? true) ? 'auto' : 'never',\n escapeFormulas: options.escapeFormulas,\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 formatDelimitedValue(processed, format);\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 = resolveCellValue(row, col);\n if (params.processCell) {\n value = params.processCell(value, col.field, row);\n }\n return formatDelimitedValue(value, format);\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 { createFieldReader } from '../../core/internal/value-accessor';\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.groupHeaderStyle) registry.register(config.groupHeaderStyle);\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 const read = createFieldReader(col.field);\n\n for (let i = 0; i < sampleSize; i++) {\n const val = read(rows[i]);\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 { resolveCellValue } from '../../core/internal/value-accessor';\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, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&apos;');\n}\n\n/** Style registry produced by {@link buildStyleRegistry}. */\ntype StyleRegistry = ReturnType<typeof buildStyleRegistry>;\n/** Excel style options resolved from {@link ExportParams.excelStyles}. */\ntype ExcelStyles = NonNullable<ExportParams['excelStyles']>;\n\n/**\n * Emit the `<Styles>` block, pre-registering every dynamic `cellStyle` result so\n * the styles referenced by data cells actually exist in the workbook.\n */\nfunction buildStylesXml(registry: StyleRegistry, styles: ExcelStyles, rows: any[], columns: ColumnConfig[]): string {\n if (styles.cellStyle) {\n for (const row of rows) {\n for (const col of columns) {\n const dynamic = styles.cellStyle(resolveCellValue(row, col), col.field, row);\n if (dynamic) registry.register(dynamic);\n }\n }\n }\n return registry.toXml();\n}\n\n/**\n * Plugin-contributed header rows (e.g. column groups) rendered above the leaf\n * header. Each row's cells must independently span the full column count;\n * `span > 1` becomes `ss:MergeAcross=\"span-1\"` and Excel implicitly fills the\n * next `span-1` cell slots from the merge.\n */\nfunction buildPluginHeaderRowsXml(params: ExportParams, groupHeaderStyleId: string | undefined): string {\n if (!params.headerRows?.length) return '';\n\n const styleAttr = groupHeaderStyleId ? ` ss:StyleID=\"${groupHeaderStyleId}\"` : '';\n let xml = '';\n for (const headerRow of params.headerRows) {\n xml += '\\n<Row>';\n for (const cell of headerRow.cells) {\n const span = Math.max(1, cell.span | 0);\n const mergeAttr = span > 1 ? ` ss:MergeAcross=\"${span - 1}\"` : '';\n xml += `<Cell${styleAttr}${mergeAttr}><Data ss:Type=\"String\">${escapeXml(cell.label ?? '')}</Data></Cell>`;\n }\n xml += '</Row>';\n }\n return xml;\n}\n\n/** The leaf header row, one cell per exported column. */\nfunction buildHeaderRowXml(columns: ColumnConfig[], params: ExportParams, headerStyleId: string | undefined): string {\n const styleAttr = headerStyleId ? ` ss:StyleID=\"${headerStyleId}\"` : '';\n let 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${styleAttr}><Data ss:Type=\"String\">${escapeXml(processed)}</Data></Cell>`;\n }\n return xml + '</Row>';\n}\n\n/** Map a resolved cell value onto an Excel `ss:Type` + already-escaped payload. */\nfunction toExcelData(value: unknown): { type: 'Number' | 'String' | 'DateTime'; displayValue: string } {\n if (value == null) return { type: 'String', displayValue: '' };\n if (typeof value === 'number' && !isNaN(value)) return { type: 'Number', displayValue: String(value) };\n if (value instanceof Date) return { type: 'DateTime', displayValue: value.toISOString() };\n return { type: 'String', displayValue: escapeXml(String(value)) };\n}\n\n/** All data rows, applying `processCell` and per-cell style resolution. */\nfunction buildDataRowsXml(\n rows: any[],\n columns: ColumnConfig[],\n params: ExportParams,\n registry: StyleRegistry | undefined,\n styles: ExcelStyles | undefined,\n): string {\n let xml = '';\n for (const row of rows) {\n xml += '\\n<Row>';\n for (const col of columns) {\n let value = resolveCellValue(row, col);\n if (params.processCell) value = params.processCell(value, col.field, row);\n\n const { type, displayValue } = toExcelData(value);\n\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 return xml;\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 && styles) xml += buildStylesXml(registry, styles, rows, columns);\n\n xml += '\\n<Worksheet ss:Name=\"Sheet1\">\\n<Table>';\n\n // Column widths\n if (styles) xml += buildColumnWidthsXml(columns, rows as Record<string, unknown>[], styles);\n\n const headerStyleId = styles?.headerStyle && registry ? registry.getStyleId(styles.headerStyle) : undefined;\n // Group-header style ID (for plugin-contributed rows). Falls back to\n // headerStyleId so users get sensible defaults without configuring twice.\n const groupHeaderStyleId =\n styles?.groupHeaderStyle && registry ? registry.getStyleId(styles.groupHeaderStyle) : headerStyleId;\n\n if (params.includeHeaders !== false) {\n xml += buildPluginHeaderRowsXml(params, groupHeaderStyleId);\n xml += buildHeaderRowXml(columns, params, headerStyleId);\n }\n\n xml += buildDataRowsXml(rows, columns, params, registry, styles);\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\nimport { formatDateValue } from '../../core/internal/utils';\nimport { resolveCellValue } from '../../core/internal/value-accessor';\nimport { BaseGridPlugin, type PluginManifest, type PluginQuery } from '../../core/plugin/base-plugin';\nimport { type CollectHeaderRowsContext, type HeaderRowCell, type HeaderRowContribution } from '../../core/plugin/types';\nimport type { ColumnConfig, InternalGrid } from '../../core/types';\nimport { resolveColumns, resolveRows } from '../shared/data-collection';\nimport { buildCsv, type CsvOptions, downloadBlob, downloadCsv } from './csv';\nimport { buildExcelXml, downloadExcel } from './excel';\nimport type { ExportCompleteDetail, ExportConfig, ExportFormat, ExportMode, ExportParams } from './types';\n\n/**\n * Subset of {@link ExportParams} accepted by the pure formatters\n * ({@link ExportPlugin.formatCsv} / {@link ExportPlugin.formatExcel}).\n *\n * The formatters operate on already-resolved data, so options that affect\n * value resolution (`mode`, `rowIndices`, `format`, `fileName`,\n * `fileExtension`) are intentionally excluded — if you need them, run them\n * through {@link ExportPlugin.export} first or call the download/export\n * methods.\n *\n * `processCell` **is** honoured: it runs once per cell on the values you pass\n * in, just like the download methods.\n * @since 2.4.0\n */\nexport type FormatCsvParams = Pick<ExportParams, 'columns' | 'includeHeaders' | 'processCell' | 'processHeader'>;\n\n/**\n * Subset of {@link ExportParams} accepted by {@link ExportPlugin.formatExcel}.\n * Same shape as {@link FormatCsvParams} plus `excelStyles`.\n * @since 2.4.0\n */\nexport type FormatExcelParams = FormatCsvParams & Pick<ExportParams, 'excelStyles'>;\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 * @since 0.1.1\n */\nexport class ExportPlugin extends BaseGridPlugin<ExportConfig> {\n /**\n * Plugin manifest — declares queries for inter-plugin communication.\n * @internal\n */\n static override readonly manifest: PluginManifest = {\n queries: [{ type: 'export:csv', description: 'Triggers a CSV export' }],\n };\n\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 Query System\n\n /** @internal */\n override handleQuery(query: PluginQuery): unknown {\n if (query.type === 'export:csv') {\n this.exportCsv();\n return true;\n }\n return undefined;\n }\n // #endregion\n\n // #region Private Methods\n\n /**\n * Resolve the columns and rows that an export with these params would\n * include, plus a fully-defaulted ExportParams for downstream consumers.\n *\n * Honours `config.onlyVisible`, `config.onlySelected`, `params.columns`, and\n * `params.rowIndices`.\n */\n private resolveExportData(\n format: ExportFormat,\n params?: Partial<ExportParams>,\n ): { columns: ColumnConfig[]; rows: Record<string, unknown>[]; fullParams: ExportParams } {\n const config = this.config;\n\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 processHeaderRow: params?.processHeaderRow,\n mode: params?.mode ?? 'raw',\n columns: params?.columns,\n rowIndices: params?.rowIndices,\n excelStyles: params?.excelStyles,\n fileExtension: params?.fileExtension,\n headerRows: params?.headerRows,\n };\n\n const columns = resolveColumns(this.columns, params?.columns, config.onlyVisible) as ColumnConfig[];\n\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 // Resolve header rows.\n //\n // Two entry paths:\n // 1. Auto-collect: broadcast `collectHeaderRows` to all plugins.\n // 2. Manual: caller passed `params.headerRows` directly (typical for\n // pure-formatter usage where there's no grid to query).\n //\n // Both paths run through the same processing pipeline so\n // `processHeaderRow`, span validation, and blank-row dropping behave\n // identically regardless of how the rows arrived.\n //\n // All gated on `includeHeaders`: `false` skips both leaf AND contributed\n // rows (all-or-nothing per #314 design).\n if (fullParams.includeHeaders === false) {\n fullParams.headerRows = undefined;\n } else {\n const raw = fullParams.headerRows ?? this.#collectHeaderRows(columns);\n fullParams.headerRows = this.#processHeaderRows(raw, columns, fullParams.processHeaderRow);\n }\n\n return { columns, rows, fullParams };\n }\n\n /**\n * Broadcast the `'collectHeaderRows'` query to all plugins and flatten the\n * responses. Each plugin may return a single contribution or an array of\n * contributions (one per row) — both shapes are flattened to a single\n * `HeaderRowContribution[]`.\n */\n #collectHeaderRows(columns: ColumnConfig[]): HeaderRowContribution[] {\n if (columns.length === 0) return [];\n const grid = this.grid as { query?: <T>(type: string, context: CollectHeaderRowsContext) => T[] } | undefined;\n if (!grid?.query) return [];\n const responses = grid.query<HeaderRowContribution | HeaderRowContribution[] | undefined>('collectHeaderRows', {\n columns,\n });\n if (!responses || responses.length === 0) return [];\n const flat: HeaderRowContribution[] = [];\n for (const r of responses) {\n if (!r) continue;\n if (Array.isArray(r)) {\n for (const row of r) {\n if (row && Array.isArray(row.cells) && row.cells.length > 0) flat.push(row);\n }\n } else if (Array.isArray(r.cells) && r.cells.length > 0) {\n flat.push(r);\n }\n }\n return flat;\n }\n\n /**\n * Normalize, validate, and filter a list of header-row contributions.\n *\n * - Applies the optional `processHeaderRow` callback per cell. `null`\n * returns blank the cell (span preserved); rows where every cell ends\n * up blank are dropped (`#314` \"collapse fully-blank row\" rule).\n * - Validates that each row's spans sum to `columns.length`. Rows that\n * under-fill are padded with a trailing blank cell; rows that\n * over-fill (malformed third-party contribution) are skipped to keep\n * downstream formatters structurally sound.\n *\n * Returns `undefined` (not `[]`) when no row survives so downstream\n * formatters can short-circuit with a single `headerRows?.length` check.\n */\n #processHeaderRows(\n rows: HeaderRowContribution[] | undefined,\n columns: ColumnConfig[],\n processHeaderRow?: (cell: HeaderRowCell, rowIndex: number) => HeaderRowCell | null,\n ): HeaderRowContribution[] | undefined {\n if (!rows || rows.length === 0) return undefined;\n const expectedSpan = columns.length;\n\n const processed: HeaderRowContribution[] = [];\n for (let rowIndex = 0; rowIndex < rows.length; rowIndex++) {\n const row = rows[rowIndex];\n if (!row || !Array.isArray(row.cells) || row.cells.length === 0) continue;\n\n const outCells: HeaderRowCell[] = [];\n let anyVisible = false;\n let runningSpan = 0;\n let overflowed = false;\n for (const cell of row.cells) {\n const span = Math.max(1, cell.span | 0);\n if (runningSpan + span > expectedSpan) {\n // Malformed: cumulative span exceeds column count. Skip the row.\n overflowed = true;\n break;\n }\n runningSpan += span;\n const next = processHeaderRow ? processHeaderRow(cell, rowIndex) : cell;\n if (next === null) {\n outCells.push({ label: '', span });\n } else {\n outCells.push({ ...next, span });\n if ((next.label ?? '') !== '') anyVisible = true;\n }\n }\n if (overflowed) continue;\n // Pad under-filled rows with a trailing blank cell so leaf headers\n // stay aligned. (A well-behaved contribution from the grouping plugin\n // always fully covers `columns.length`; this guards against\n // third-party plugins that drift.)\n if (runningSpan < expectedSpan) {\n outCells.push({ label: '', span: expectedSpan - runningSpan });\n }\n if (anyVisible) processed.push({ cells: outCells });\n // else: every cell collapsed → row dropped.\n }\n\n return processed.length > 0 ? processed : undefined;\n }\n\n /**\n * Apply the configured export `mode` to a single cell value.\n *\n * - `'raw'` : returns the underlying value (after optional `processCell`).\n * - `'formatted'` : applies the column-type default formatter and `column.format`,\n * returning the same string the grid displays. `processCell`\n * runs last on the formatted value.\n */\n private resolveCellOutput(\n value: unknown,\n col: ColumnConfig,\n row: Record<string, unknown>,\n mode: ExportMode,\n processCell?: ExportParams['processCell'],\n ): unknown {\n let out: unknown = value;\n\n if (mode === 'formatted') {\n const formatFn = this.#resolveFormatFn(col);\n if (formatFn) {\n try {\n const formatted = formatFn(value, row);\n out = formatted == null ? '' : String(formatted);\n } catch {\n out = value == null ? '' : String(value);\n }\n } else if (col.type === 'date') {\n out = formatDateValue(value);\n } else if (col.type === 'boolean') {\n out = !!value;\n } else {\n out = value;\n }\n }\n\n if (processCell) {\n out = processCell(out, col.field, row);\n }\n\n return out;\n }\n\n private performExport(format: ExportFormat, params?: Partial<ExportParams>): void {\n const { columns, rows, fullParams } = this.resolveExportData(format, params);\n\n this.isExportingFlag = true;\n let fileName = fullParams.fileName ?? 'export';\n\n // buildCsv / buildExcelXml read each cell via `resolveCellValue(row, col)`.\n // Our pre-resolved row objects are keyed by `column.field`, so any\n // `valueAccessor` on the column would misfire on these synthetic rows.\n // Strip accessors before delegating to keep the lookup as a plain field read.\n const downstreamColumns = this.#stripAccessors(columns);\n\n try {\n switch (format) {\n case 'csv': {\n const data = this.#buildExportData(rows, columns, fullParams);\n // processCell already applied during data resolution — strip it so buildCsv\n // doesn't double-apply.\n const content = buildCsv(data, downstreamColumns, { ...fullParams, processCell: undefined }, { bom: true });\n fileName = fileName.endsWith('.csv') ? fileName : `${fileName}.csv`;\n downloadCsv(content, fileName);\n break;\n }\n\n case 'excel': {\n const data = this.#buildExportData(rows, columns, fullParams);\n const content = buildExcelXml(data, downstreamColumns, { ...fullParams, processCell: undefined });\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 data = this.#buildExportData(rows, columns, fullParams);\n // When plugins contributed header rows (e.g. column groups), emit an\n // envelope `{ headerRows, rows }` so consumers can reconstruct\n // multi-level headers. Without contributions the output stays a flat\n // array — non-breaking for existing JSON consumers.\n const payload =\n fullParams.headerRows && fullParams.headerRows.length > 0\n ? { headerRows: fullParams.headerRows, rows: data }\n : data;\n const content = JSON.stringify(payload, 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 /**\n * Resolve every cell into a plain object keyed by `column.field`, applying\n * the configured `mode` and `processCell` once. The result can be fed to\n * `buildCsv` / `buildExcelXml` directly — they read values via\n * `resolveCellValue`, which on plain key/value objects is a simple\n * `row[field]` lookup, so no double-resolution occurs.\n */\n #buildExportData(\n rows: Record<string, unknown>[],\n columns: ColumnConfig[],\n params: ExportParams,\n ): Record<string, unknown>[] {\n const mode = params.mode ?? 'raw';\n return rows.map((row) => {\n const obj: Record<string, unknown> = {};\n for (const col of columns) {\n const raw = resolveCellValue(row, col);\n obj[col.field] = this.resolveCellOutput(raw, col, row, mode, params.processCell);\n }\n return obj;\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 * Returns the row data that would be included in an export, without\n * producing a file. Honours `mode`, `onlyVisible`, `onlySelected`,\n * `columns`, `rowIndices`, `processCell`, and (for `mode: 'formatted'`)\n * `column.format`.\n *\n * Each returned object is keyed by `column.field` in column order.\n *\n * @example\n * ```ts\n * // Underlying typed values (Date stays Date, number stays number)\n * const raw = exporter.export();\n *\n * // What the user sees in each cell\n * const display = exporter.export({ mode: 'formatted' });\n * ```\n */\n export(params?: Partial<ExportParams>): Record<string, unknown>[] {\n const { columns, rows, fullParams } = this.resolveExportData('json', params);\n return this.#buildExportData(rows, columns, fullParams);\n }\n\n /**\n * Returns the columns (in order) that an export with these params would\n * include. Useful when handing rows to a third-party serializer that needs\n * header labels, widths, or types alongside the data.\n */\n getResolvedColumns(params?: Partial<ExportParams>): ColumnConfig[] {\n return resolveColumns(this.columns, params?.columns, this.config.onlyVisible) as ColumnConfig[];\n }\n\n /**\n * Format an array of row objects as a CSV string. Column order, headers,\n * and which fields to emit are taken from the plugin's resolved columns\n * (respecting `onlyVisible` and `params.columns`).\n *\n * This is a **pure formatter** — it does not re-resolve values from the\n * grid. Pass `data` produced by {@link ExportPlugin.export} (or any\n * compatible row objects keyed by `column.field`).\n *\n * `params.processCell` is honoured: it runs once per cell on the values in\n * `data`. `mode` is **not** accepted here — apply it upstream via\n * `export({ mode: 'formatted' })`.\n *\n * @example\n * ```ts\n * const csv = exporter.formatCsv(exporter.export());\n * await navigator.clipboard.writeText(csv);\n * ```\n */\n formatCsv(data: Record<string, unknown>[], params?: FormatCsvParams, options?: CsvOptions): string {\n const { columns, fullParams } = this.resolveExportData('csv', params);\n return buildCsv(data, this.#stripAccessors(columns), fullParams, options);\n }\n\n /**\n * Format an array of row objects as an Excel XML Spreadsheet 2003 string.\n * See {@link formatCsv} for the data-shape contract — this method is also a\n * pure formatter and `params.processCell` is honoured the same way.\n */\n formatExcel(data: Record<string, unknown>[], params?: FormatExcelParams): string {\n const { columns, fullParams } = this.resolveExportData('excel', params);\n return buildExcelXml(data, this.#stripAccessors(columns), fullParams);\n }\n\n /** @internal Drop `valueAccessor` so downstream builders read pre-resolved fields. */\n #stripAccessors(columns: ColumnConfig[]): ColumnConfig[] {\n return columns.map((c) => (c.valueAccessor ? ({ ...c, valueAccessor: undefined } as ColumnConfig) : c));\n }\n\n /**\n * @internal Resolve the format function for a column for `mode: 'formatted'`.\n * Mirrors the priority chain in core/internal/rows.ts (`column.format` → adapter\n * type default), but inlined so this module stays free of browser-only imports.\n */\n #resolveFormatFn(col: ColumnConfig): ((value: unknown, row: unknown) => string) | undefined {\n if (col.format) return col.format as (value: unknown, row: unknown) => string;\n if (!col.type) return undefined;\n // The grid host carries optional `__frameworkAdapter` and `_hostElement` from\n // InternalGrid. Narrow with a structural guard rather than an `as unknown as`\n // cast — these keys are documented public-internal in core/types.ts.\n const grid = this.grid as Partial<Pick<InternalGrid<unknown>, '__frameworkAdapter' | '_hostElement'>> | undefined;\n const adapter = grid?.__frameworkAdapter;\n if (!adapter?.getTypeDefault) return undefined;\n const appDefault = adapter.getTypeDefault(col.type, grid?._hostElement);\n return appDefault?.format as ((value: unknown, row: unknown) => string) | undefined;\n }\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"],"mappings":"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"}